Java 自动装箱和拆箱还有包装类的缓存问题

发布于:2025-05-27 ⋅ 阅读:(156) ⋅ 点赞:(0)

自动装箱和拆箱就是将基本数据类型和包装类之间进行自动的互相转换。JDK1.5 后,
Java 引入了自动装箱(autoboxing)/拆箱(unboxing)。

自动装箱:
基本类型的数据处于需要对象的环境中时,会自动转为“对象”。
我们以 Integer 为例:
在 JDK1.5 以前,这样的代码 Integer i = 5 是错误的,必须要通过 Integer i = new
Integer(5) 这样的语句来实现基本数据类型转换成包装类的过程;
而在 JDK1.5 以后,Java 提供了自动装箱的功能,因此只需 Integer i = 5 这样的语句
就能实现基本数据类型转换成包装类,这是因为 JVM 为我们执行了 Integer i =
Integer.valueOf(5)这样的操作,这就是 Java 的自动装箱。
自动拆箱:
每当需要一个值时,对象会自动转成基本数据类型,没必要再去显式调用 intValue()、
doubleValue()等转型方法。
如 Integer i = 5;int j = i; 这样的过程就是自动拆箱。
我们可以用一句话总结自动装箱/拆箱:
自动装箱过程是通过调用包装类的 valueOf()方法实现的,而自动拆箱过程是通过调用
包装类的 xxxValue()方法实现的(xxx 代表对应的基本数据类型,如 intValue()、
doubleValue()等)。

自动装箱与拆箱的功能事实上是编译器来帮的忙,编译器在编译时依据您所编写的语
法,决定是否进行装箱或拆箱动作。

自动装箱:

Integer i = 100;//自动装箱
//相当于编译器自动为您作以下的语法编译:
Integer i = Integer. valueOf (100);//调用的是 valueOf(100),而不是 new Integer(100)

自动拆箱:

Integer i = 100;
int j = i;//自动拆箱
//相当于编译器自动为您作以下的语法编译:
int j = i.intValue();

所以自动装箱与拆箱的功能是所谓的“编译器蜜糖(Compiler Sugar)”,虽然使用这
个功能很方便,但在程序运行阶段您得了解 Java 的语义:

包装类空指针异常问题:

public class Test1 {
    public static void main(String[ ] args) {
        Integer i = null;
        int j = i;
    }
}

 

null 表示 i 没有指向任何对象的实体,但作为对象名称是合法的(不管这个对象名称存
是否指向了某个对象的实体)。由于实际上 i 并没有指向任何对象的实体,所以也就不可能
操作 intValue()方法,这样上面的写法在运行时就会出现 NullPointerException 错误。

包装类的缓存问题:

整型、char类型所对应的包装类,在自动装箱时,对于-128~127之间的值会进行缓存
处理,其目的是提高效率。
缓存处理的原理为:如果数据在-128~127这个区间,那么在类加载时就已经为该区间
的每个数值创建了对象,并将这256个对象存放到一个名为cache的数组中。每当自动装箱
过程发生时(或者手动调用valueOf()时),就会先判断数据是否在该区间,如果在则直接
获取数组中对应的包装类对象的引用,如果不在该区间,则会通过new调用包装类的构造方法来创建对象。

下面我们以Integer类为例,看一看Java为我们提供的源码,加深对缓存技术的理解:

public static Integer valueOf(int i) {
if (i >= IntegerCache. low && i <= IntegerCache. high )
return IntegerCache. cache [i + (-IntegerCache. low )];
return new Integer(i);
}

 这段代码中我们需要解释下面几个问题:
1. IntegerCache类为Integer类的一个静态内部类,仅供Integer类使用。
2. 一般情况下 IntegerCache.low为-128,IntegerCache.high为127,

IntegerCache.cache为内部类的一个静态属性:

private static class IntegerCache {
    static final int low = -128;
    static final int high ;
    static final Integer cache [ ];
    static {
        // high value may be configured by property
        int h = 127;
        String integerCacheHighPropValue =
        sun.misc.VM. getSavedProperty ("java.lang.Integer.IntegerCache.high");
        if (integerCacheHighPropValue != null) {
            try {
                int i = parseInt (integerCacheHighPropValue);
                i = Math. max (i, 127);
                // Maximum array size is Integer.MAX_VALUE
                h = Math. min (i, Integer. MAX_VALUE - (- low ) -1);
            } catch( NumberFormatException nfe) {
                // If the property cannot be parsed into an int, ignore it.
            }
        }
        high = h;
        cache = new Integer[( high - low ) + 1];
        int j = low ;
        for(int k = 0; k < cache .length; k++)
            cache [k] = new Integer(j++);
            // range [-128, 127] must be interned (JLS7 5.1.7)
            assert IntegerCache. high >= 127;
        }
        private IntegerCache() {}
}

 由上面的源码我们可以看到,静态代码块的目的就是初始化数组cache的,这个过程会
在类加载时完成。

下面我们做一下代码测试

包装类的缓存测试:

public class Test3 {
    public static void main(String[ ] args) {
        Integer in1 = -128;
        Integer in2 = -128;
        System. out .println(in1 == in2);//true 因为 123 在缓存范围内
        System. out .println(in1.equals(in2));//true
        Integer in3 = 1234;
        Integer in4 = 1234;
        System. out .println(in3 == in4);//false 因为 1234 不在缓存范围内
        System. out .println(in3.equals(in4));//true

执行后的结果: 

 

 如下是Test3的内存分析:


微信公众号

今日签到

点亮在社区的每一天
去签到