Java包装类详解(二)

159 阅读7分钟

本文已参与「新人创作礼」活动,一起开启掘金创作之路。

面试相关问题

1. 下面这段代码的输出结果是什么?

public class Main {
    public static void main(String[] args) {
         
        Integer i1 = 100;
        Integer i2 = 100;
        Integer i3 = 200;
        Integer i4 = 200;
         
        System.out.println(i1==i2);  
        System.out.println(i3==i4);  
    }
}

答案:

true
false

输出结果表明i1i2指向的是同一个对象,而i3i4指向的是不同的对象。Java 8中IntegervalueOf()方法的具体实现如下:

// IntegerCache.low = -128
// IntegerCache.high 可通过JVM参数 -XX:AutoBoxCacheMax=<size> 设置,必须大于等于127,默认为127
public static Integer valueOf(int i) {
    if (i >= IntegerCache.low && i <= IntegerCache.high)
        return IntegerCache.cache[i + (-IntegerCache.low)];
    return new Integer(i);
}

其中IntegerCache是一个私有静态内部类,其实现如下:

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() {}
}

IntegerCache表示Integer缓存,其中的cache变量是一个静态Integer数组,在静态初始化块中被初始化,默认情况下,保存了-128~127共256个整数对应的Integer对象。

valueOf代码中,如果数值位于被缓存的范围,则直接从IntegerCache中获取已预先创建的Integer对象,只有不在缓存范围时,才通过new创建对象。

通过共享常用对象,可以节省内存空间,由于Integer是不可变的,所以缓存的对象可以安全的被共享。Boolean、Byte、Short、Long、Character都有类似的实现。这种共享常用对象的思路,叫享元模式,英文叫Flyweight,即共享的轻量级元素。

2. 下面这段代码的输出结果是什么?

public class Main {
    public static void main(String[] args) {
         
        Double i1 = 100.0;
        Double i2 = 100.0;
        Double i3 = 200.0;
        Double i4 = 200.0;
         
        System.out.println(i1==i2); 
        System.out.println(i3==i4);  
    }
}

答案:

false
false

Java 8中DoublevalueOf()方法的具体实现如下,不难理解答案为何都是false

public static Double valueOf(double d) {
    return new Double(d);
}

为什么Double类的valueOf()方法会采用与Integer类的valueOf()方法不同的实现。很简单:在某个范围内的整型数值的个数是有限的,而浮点数却不是

需要注意的是,Integer、Short、Byte、Character、Long这几个类的valueOf()方法的实现是类似的:

public static Byte valueOf(byte b) {
    final int offset = 128;
    return ByteCache.cache[(int)b + offset];
}

public static Long valueOf(long l) {
    final int offset = 128;
    if (l >= -128 && l <= 127) { // will cache
        return LongCache.cache[(int)l + offset];
    }
    return new Long(l);
}

public static Character valueOf(char c) {
    if (c <= 127) { // must cache
        return CharacterCache.cache[(int)c];
    }
    return new Character(c);
}

public static Short valueOf(short s) {
    final int offset = 128;
    int sAsInt = s;
    if (sAsInt >= -128 && sAsInt <= 127) { // must cache
        return ShortCache.cache[sAsInt + offset];
    }
    return new Short(s);
}

Double、FloatvalueOf()方法的实现是类似的:

public static Float valueOf(float f) {
    return new Float(f);
}

3. 下面这段代码的输出结果是什么?

public class Main {
    public static void main(String[] args) {
         
        Boolean i1 = false;
        Boolean i2 = false;
        Boolean i3 = true;
        Boolean i4 = true;
         
        System.out.println(i1==i2); 
        System.out.println(i3==i4); 
    }
}

答案:

true
true

看完Boolean类的valueOf方法的实现后,答案同样一目了然!

/**
* The {@code Boolean} object corresponding to the primitive
* value {@code true}.
*/
public static final Boolean TRUE = new Boolean(true);

/**
* The {@code Boolean} object corresponding to the primitive
* value {@code false}.
*/
public static final Boolean FALSE = new Boolean(false);

public static Boolean valueOf(boolean b) {
    return (b ? TRUE : FALSE);
}

4. Integer i = new Integer(10)Integer i = 10这两种方式的区别

  • 第一种方式不会触发自动装箱;第二种方式会;
  • 从执行效率和资源占用上来讲,new每次都会创建一个新对象,而除了FloatDouble外的其他包装类,都会缓存包装类对象,减少需要创建对象的次数,节省空间,提升性能。
  • 从Java 9开始,这些构造方法已经被标记为过时了,推荐使用静态的valueOf方法。

5. 下面程序的输出结果是什么?

public class Main {
    public static void main(String[] args) {         
        Integer a = 1;
        Integer b = 2;
        Integer c = 3;
        Integer d = 3;
        Integer e = 321;
        Integer f = 321;
        Long g = 3L;
        Long h = 2L;         
        System.out.println(c==d);
        System.out.println(e==f);
        System.out.println(c==(a+b));
        System.out.println(c.equals(a+b));
        System.out.println(g==(a+b));
        System.out.println(g.equals(a+b));
        System.out.println(g.equals(a+h));
    }
}

答案:

System.out.println(c==d);               // true
System.out.println(e==f);               // false
System.out.println(c==(a+b));           // true
System.out.println(c.equals(a+b));      // true
System.out.println(g==(a+b));           // true
System.out.println(g.equals(a+b));      // false
System.out.println(g.equals(a+h));      // true
  • c==de == f 的结果不多解释了。

  • System.out.println(c==(a+b))对应的字节码如下:

     91 aload_3         // 将c压入操作数栈
     92 invokevirtual #10 <java/lang/Integer.intValue>    // 自动拆箱
     95 aload_1         // 将a压入操作数栈
     96 invokevirtual #10 <java/lang/Integer.intValue>    // 自动拆箱
     99 aload_2         // 将b压入操作数栈
    100 invokevirtual #10 <java/lang/Integer.intValue>    // 自动拆箱
    103 iadd            // 计算a+b的值并将结果压入操作数栈
    104 if_icmpne 111 (+7)   // 比较 c 和 a+b 的值
    107 iconst_1
    108 goto 112 (+4)
    111 iconst_0
    112 invokevirtual #9 <java/io/PrintStream.println>
    115 getstatic #8 <java/lang/System.out>
    

    ==运算符的两个操作数都是包装器类型的引用时,则比较的是引用地址,而如果其中有一个操作数是表达式(即包含算术运算符)则比较的是数值(即会触发自动拆箱过程),从对应的字节码也可以看出调用了intValue()方法,触发了自动拆箱,比较它们的数值是否相等,因此c==(a+b)的结果为true

  • System.out.println(c.equals(a+b))对应的字节码如下:

    118 aload_3     // 将c压入操作数栈
    119 aload_1     // 将a压入操作数栈
    120 invokevirtual #10 <java/lang/Integer.intValue> // 自动拆箱
    123 aload_2     // 将b压入操作数栈
    124 invokevirtual #10 <java/lang/Integer.intValue> // 自动拆箱 
    127 iadd        // 计算a+b的值并将结果压入操作数栈
    128 invokestatic #2 <java/lang/Integer.valueOf>    // 将a+b的值的自动装箱
    131 invokevirtual #11 <java/lang/Integer.equals>   // 调用equals方法
    134 invokevirtual #9 <java/io/PrintStream.println>
    137 getstatic #8 <java/lang/System.out>
    

    从字节码可以看出,c.equals(a+b)首先触发自动拆箱,又触发了自动装箱,再调用equals方法,而所有的包装类都重写了Object类中的equals方法,equals用于判断当前对象和参数传入的对象是否相同,Object类的默认实现是比较地址,它和比较运算符(==)的结果是一样的。

    equals应该反映的是对象间的逻辑相等关系,所以包装类都重写了该实现,实际比较用的是其包装的基本类型值,对于Integer类,其equals方法代码如下(Java 8):

    public boolean equals(Object obj) {
        if (obj instanceof Integer) {
            return value == ((Integer)obj).intValue();
        }
        return false;
    }
    

    因此,c.equals(a+b)的结果为true.

  • System.out.println(g==(a+b))对应的字节码如下:

    140 aload 7       // 将g压入操作数栈
    142 invokevirtual #12 <java/lang/Long.longValue>    // 自动拆箱
    145 aload_1       // 将a压入操作数栈 
    146 invokevirtual #10 <java/lang/Integer.intValue>  // 自动拆箱
    149 aload_2       // 将b压入操作数栈
    150 invokevirtual #10 <java/lang/Integer.intValue>  // 自动拆箱
    153 iadd          // 计算a+b的值并将结果压入操作数栈
    154 i2l           // 将 a+b 的结果从 int 转换为 long
    155 lcmp          // 比较 g 和 a+b 的值
    156 ifne 163 (+7) 
    159 iconst_1
    160 goto 164 (+4)
    163 iconst_0
    164 invokevirtual #9 <java/io/PrintStream.println>
    167 getstatic #8 <java/lang/System.out>
    

    g==(a+b)的比较过程和c==(a+b)类似,多了一个类型转换,都是比较数值,因此结果为true.

  • System.out.println(g.equals(a+b))对应的字节码如下:

    170 aload 7        // 将g压入操作数栈
    172 aload_1        // 将a压入操作数栈
    173 invokevirtual #10 <java/lang/Integer.intValue>  // 自动拆箱
    176 aload_2        // 将b压入操作数栈
    177 invokevirtual #10 <java/lang/Integer.intValue>  // 自动拆箱
    180 iadd           // 计算a+b的值并将结果压入操作数栈
    181 invokestatic #2 <java/lang/Integer.valueOf>     // 自动装箱
    184 invokevirtual #13 <java/lang/Long.equals>       // 调用equals比较
    187 invokevirtual #9 <java/io/PrintStream.println>
    190 getstatic #8 <java/lang/System.out>
    

    对于Long类,其equals方法代码如下(Java 8):

    public boolean equals(Object obj) {
        if (obj instanceof Long) {
        	return value == ((Long)obj).longValue();
        }
        return false;
    }
    

    从字节码可以看出,g.equals(a+b)也进行了自动拆箱、装箱过程,但是a+b装箱后为Integer类型,而gLong类型,因此调用equasl会输出false.

  • System.out.println(g.equals(a+h))对应的字节码如下:

    193 aload 7           // 将g压入操作数栈
    195 aload_1           // 将a压入操作数栈
    196 invokevirtual #10 <java/lang/Integer.intValue> // 自动拆箱
    199 i2l               // 从a的数值从 int 转换成 long
    200 aload 8           // 将h压入操作数栈
    202 invokevirtual #12 <java/lang/Long.longValue>  // 自动拆箱
    205 ladd              // 计算a+h的值并将结果压入操作数栈
    206 invokestatic #5 <java/lang/Long.valueOf>       // 自动装箱
    209 invokevirtual #13 <java/lang/Long.equals>      // 调用equals比较
    212 invokevirtual #9 <java/io/PrintStream.println>
    

    g.equals(a+h)相比于g.equals(a+b)多了一步类型转换,a+h装箱后的类型为Long,因此结果为true.

- main方法对应的字节码如下:
   ```java
    0 iconst_1
     1 invokestatic #2 <java/lang/Integer.valueOf>
     4 astore_1
     5 iconst_2
     6 invokestatic #2 <java/lang/Integer.valueOf>
     9 astore_2
    10 iconst_3
    11 invokestatic #2 <java/lang/Integer.valueOf>
    14 astore_3
    15 iconst_3
    16 invokestatic #2 <java/lang/Integer.valueOf>
    19 astore 4
    21 sipush 321
    24 invokestatic #2 <java/lang/Integer.valueOf>
    27 astore 5
    29 sipush 321
    32 invokestatic #2 <java/lang/Integer.valueOf>
    35 astore 6
    37 ldc2_w #3 <3>
    40 invokestatic #5 <java/lang/Long.valueOf>
    43 astore 7
    45 ldc2_w #6 <2>
    48 invokestatic #5 <java/lang/Long.valueOf>
    51 astore 8
    53 getstatic #8 <java/lang/System.out>
    56 aload_3
    57 aload 4
    59 if_acmpne 66 (+7)
    62 iconst_1
    63 goto 67 (+4)
    66 iconst_0
    67 invokevirtual #9 <java/io/PrintStream.println>
    70 getstatic #8 <java/lang/System.out>
    73 aload 5
    75 aload 6
    77 if_acmpne 84 (+7)
    80 iconst_1
    81 goto 85 (+4)
    84 iconst_0
    85 invokevirtual #9 <java/io/PrintStream.println>
    88 getstatic #8 <java/lang/System.out>
    91 aload_3
    92 invokevirtual #10 <java/lang/Integer.intValue>
    95 aload_1
    96 invokevirtual #10 <java/lang/Integer.intValue>
    99 aload_2
   100 invokevirtual #10 <java/lang/Integer.intValue>
   103 iadd
   104 if_icmpne 111 (+7)
   107 iconst_1
   108 goto 112 (+4)
   111 iconst_0
   112 invokevirtual #9 <java/io/PrintStream.println>
   115 getstatic #8 <java/lang/System.out>
   118 aload_3
   119 aload_1
   120 invokevirtual #10 <java/lang/Integer.intValue>
   123 aload_2
   124 invokevirtual #10 <java/lang/Integer.intValue>
   127 iadd
   128 invokestatic #2 <java/lang/Integer.valueOf>
   131 invokevirtual #11 <java/lang/Integer.equals>
   134 invokevirtual #9 <java/io/PrintStream.println>
   137 getstatic #8 <java/lang/System.out>
   140 aload 7
   142 invokevirtual #12 <java/lang/Long.longValue>
   145 aload_1
   146 invokevirtual #10 <java/lang/Integer.intValue>
   149 aload_2
   150 invokevirtual #10 <java/lang/Integer.intValue>
   153 iadd
   154 i2l
   155 lcmp
   156 ifne 163 (+7)
   159 iconst_1
   160 goto 164 (+4)
   163 iconst_0
   164 invokevirtual #9 <java/io/PrintStream.println>
   167 getstatic #8 <java/lang/System.out>
   170 aload 7
   172 aload_1
   173 invokevirtual #10 <java/lang/Integer.intValue>
   176 aload_2
   177 invokevirtual #10 <java/lang/Integer.intValue>
   180 iadd
   181 invokestatic #2 <java/lang/Integer.valueOf>
   184 invokevirtual #13 <java/lang/Long.equals>
   187 invokevirtual #9 <java/io/PrintStream.println>
   190 getstatic #8 <java/lang/System.out>
   193 aload 7
   195 aload_1
   196 invokevirtual #10 <java/lang/Integer.intValue>
   199 i2l
   200 aload 8
   202 invokevirtual #12 <java/lang/Long.longValue>
   205 ladd
   206 invokestatic #5 <java/lang/Long.valueOf>
   209 invokevirtual #13 <java/lang/Long.equals>
   212 invokevirtual #9 <java/io/PrintStream.println>
   215 return
   ```

基础类型是为了单纯使用数值时节省内存空间但是无法进行相关函数操作,而包装类型是为了方便进行相关操作但有占用更多的内存空间,而有时需要用基础类型,有时又需要用包装类型,为了避免写这种重复转换的代码,才提供了自动拆箱/装箱方便操作。

Reference