看下面一段程序(来自《深入理解Java虚拟机(第三版)》P376代码清单10-13 自动装箱的陷阱)
Integer a = 1;
Integer b = 2;
Integer c = 3;
Integer d = 3;
Integer e = 321;
Integer f = 321;
Long g = 3L;
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));
结果为:
true
false
true
true
true
false
可能刚看到这个结果很迷惘,下面结合代码生成的字节码来分析一下(由于字节码很长,所以分段讲解):
第一部分, 变量的赋值
/**
当int取值-1~5采用iconst指令,
取值-128~-2||6~127采用bipush指令,
取值-32768~-129||128~32767采用sipush指令,
取值-2147483648~-32768||32768~2147483647采用 ldc 指令。
*/
// 变量a
0: iconst_1
1: invokestatic #2 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
4: astore_1
// 变量b
5: iconst_2
6: invokestatic #2 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
9: astore_2
// 变量c
10: iconst_3
11: invokestatic #2 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
14: astore_3
// 变量d
15: iconst_3
16: invokestatic #2 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
19: astore 4
// 变量e
21: sipush 321
24: invokestatic #2 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
27: astore 5
// 变量f
29: sipush 321
32: invokestatic #2 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
35: astore 6
// 变量g
37: ldc2_w #3 // long 3l
40: invokestatic #5 // Method java/lang/Long.valueOf:(J)Ljava/lang/Long;
43: astore 7
a == b
这一段的字节码如下:
// 由于对象缓存,所以c和d指向的是同一个对象,所以输出 true
45: getstatic #6 // Field java/lang/System.out:Ljava/io/PrintStream;
48: aload_3
49: aload 4
51: if_acmpne 58
54: iconst_1
55: goto 59
58: iconst_0
59: invokevirtual #7 // Method java/io/PrintStream.println:(Z)V
62: getstatic #6 // Field java/lang/System.out:Ljava/io/PrintStream;
Integer的valueOf方法
@HotSpotIntrinsicCandidate
public static Integer valueOf(int i) {
if (i >= IntegerCache.low && i <= IntegerCache.high)
return IntegerCache.cache[i + (-IntegerCache.low)];
return new Integer(i);
}
其中IntegerCache是Integer的私有静态内部类
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 =
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数组,保存着初始化时缓存的Integer对象,其下界low固定为-128, 上界high默认为127, 但是可以通过指定系统属性来调整,比如:
-Djava.lang.Integer.IntegerCache.high=321
c和d的值为3,由于Byte, Short, Integer, Long四个包装类默认情况下会缓存值为 -128 ~ 127之间的对象,Character会缓存\u00’ ~ '\u7F’之间的数据。 所以输出为true,。
但是如果c和d通过如下方式创建:
Integer c = new Integer(3);
Integer d = new Integer(3);
则 c == d为false。因为是创建的两个不同的对象,尽管值相等。但是从JDK9开始,上面的构造器就被声明为Deprecated的,原因如下:
It is rarely appropriate to use this constructor. The static factory
{@link #valueOf(int)} is generally a better choice, as it is
ikely to yield significantly better space and time performance.
e == f
这一段的字节码如下:
// 第二次输出 false
65: aload 5
67: aload 6
69: if_acmpne 76
72: iconst_1
73: goto 77
76: iconst_0
77: invokevirtual #7 // Method java/io/PrintStream.println:(Z)V
80: getstatic #6 // Field java/lang/System.out:Ljava/io/PrintStream;
在赋值时,通过执行invokespecial指令,调用Integer类的静态方法valueOf,实现自动装箱,又由于默认情况下缓存上界为127,所以返回两个不同的对象。
c == (a+b)
这一部分的字节码如下:
// 第三次输出 true
83: aload_3
84: invokevirtual #8 // Method java/lang/Integer.intValue:()I
87: aload_1
88: invokevirtual #8 // Method java/lang/Integer.intValue:()I
91: aload_2
92: invokevirtual #8 // Method java/lang/Integer.intValue:()I
95: iadd
96: if_icmpne 103 //比较的是操作栈上的数值
99: iconst_1
100: goto 104
103: iconst_0
104: invokevirtual #7 // Method java/io/PrintStream.println:(Z)V
107: getstatic #6 // Field java/lang/System.out:Ljava/io/PrintStream;
可以看到,分别将变量 c, a, b压栈,同时实行拆箱操作。紧接着执行a和b的加法操作,然后比较栈顶和次栈顶,很明显,这里的值是数值,而不是对象 的地址。因为数值相等,所以输出true。
c.equals((a+b))
这一部分的字节码如下:
// 第四次输出 equal进行值比较 true
110: aload_3
111: aload_1
112: invokevirtual #8 // Method java/lang/Integer.intValue:()I
115: aload_2
116: invokevirtual #8 // Method java/lang/Integer.intValue:()I
119: iadd
120: invokestatic #2 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
123: invokevirtual #9 // Method java/lang/Integer.equals:(Ljava/lang/Object;)Z
126: invokevirtual #7 // Method java/io/PrintStream.println:(Z)V
129: getstatic #6 // Field java/lang/System.out:Ljava/io/PrintStream;
和第三次一样,先将c, a, b入栈,不同之处是c并没有拆箱,而且a和b完成加法操作后进行了装箱。因为equals的参数必须是Object及其子类(对象)。Integer重写了equals方法:
public boolean equals(Object obj) {
if (obj instanceof Integer) {
return value == ((Integer)obj).intValue();
}
return false;
}
g == (a+b)
这一段的字节码如下:
// 第五次输出 true
132: aload 7
134: invokevirtual #10 // Method java/lang/Long.longValue:()J
137: aload_1
138: invokevirtual #8 // Method java/lang/Integer.intValue:()I
141: aload_2
142: invokevirtual #8 // Method java/lang/Integer.intValue:()I
145: iadd
146: i2l //将a+b原本为int的结果转化为long
147: lcmp
148: ifne 155 //比较的是操作栈上的数值
151: iconst_1
152: goto 156
155: iconst_0
156: invokevirtual #7 // Method java/io/PrintStream.println:(Z)V
159: getstatic #6 // Field java/lang/System.out:Ljava/io/PrintStream;
首先分别将c, a, b入栈,同时拆箱。执行加法后将结果扩展为long类型,由于栈中保存的是拆箱后的数值,所以结果为true。
g.equals(a+b)
最后一段的字节码如下:
// 第六次输出 false
162: aload 7
164: aload_1
165: invokevirtual #8 // Method java/lang/Integer.intValue:()I
168: aload_2
169: invokevirtual #8 // Method java/lang/Integer.intValue:()I
172: iadd
173: invokestatic #2 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
176: invokevirtual #11 // Method java/lang/Long.equals:(Ljava/lang/Object;)Z
179: invokevirtual #7 // Method java/io/PrintStream.println:(Z)V
182: return
同样,把c, a, b入栈,c不拆箱,a和b拆箱,然后执行加法操作,最后将结果装箱。此时栈中保存的是对象的地址,注意,这里c是Long类型的,而a+b的结果是Integer类型的。所以在执行**Long.equals(Object obj)**时,类型不同当然结果为false。
public boolean equals(Object obj) {
if (obj instanceof Long) {
return value == ((Long)obj).longValue();
}
return false;
}
总结
- 当对包装类型赋值时,调用对应包装类型的valueOf方法进行封装,然后返回一个对象。
- 某些包装类可能含有缓存对象,而且缓存区间的上界是可以修改的,别只知道缓存范围为 -128~127。
- 当包装类型对象在执行算术逻辑运算的情况下,会自动拆箱。
- 当包装类型对象在执行**==**比较的时候,包装类型并不会拆箱。当然原始类型比较时,直接是值比较,没有装箱操作。
- 这几个包装类的equals方法是两个对象之间的比较,而且必须是在相同对象的前提下,才进行值的比较。所以就算值相等,但是类型不同也返回false(如最后一个输出)。