一段代码引发的问题
最近在学习一本关于java虚拟机的书,其中有一段关于自动装箱陷阱的示例代码如下:
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;
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
}
作者并没有给出输出答案是如何,自己分析了一下,自认为应该没错,然后实践运行出来傻眼了,暗恨自己以前囫囵吞枣,没有彻底搞清楚。所以花时间好好补补课。
概念
本质上自动装箱、拆箱只是Java语言中的语法糖,在Java里使用特别广泛。我们通过一个示例看一下这些语法糖在编译后发生的变化。
public static void main(String[] args) {
Integer a = 1;//自动装箱
int b = a;//自动拆箱
}
反编译class文件后如下:
public static void main(java.lang.String[]);
Code:
0: iconst_1
1: invokestatic #2 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
4: astore_1
5: aload_1
6: invokevirtual #3 // Method java/lang/Integer.intValue:()I
9: istore_2
10: return
我们看到,在执行Integer a = 1;
这句的时候,实际上执行的是Integer的静态方法valueOf():
Integer a = Integer.valueOf(1);
在执行int b = a;
时,执行的是Integer的方法intValue():
int b = a.intValue();
这就是Java的自动状态、拆箱。
问题分析
我们看回示例代码的前两个打印:
Integer c = 3;
Integer d = 3;
Integer e = 321;
Integer f = 321;
System.out.println(c == d);//true
System.out.println(e == f);//false
what the fffffff...?都是对象进行比较,有毛的不一样?
这就是自动装箱中的一个陷阱了,我们根据字节码流程分析一下:
首先在上面的赋值操作中,我们知道是调用Integer类的valueOf(Object)方法生成Integer对象的:
Integer c = Integer.valueOf(3);
我们看一下这个valueOf()方法的实现:
/**
* Returns an {@code Integer} instance representing the specified
* {@code int} value. If a new {@code Integer} instance is not
* required, this method should generally be used in preference to
* the constructor {@link #Integer(int)}, as this method is likely
* to yield significantly better space and time performance by
* caching frequently requested values.
*
* This method will always cache values in the range -128 to 127,
* inclusive, and may cache other values outside of this range.
*
* @param i an {@code int} value.
* @return an {@code Integer} instance representing {@code i}.
* @since 1.5
*/
public static Integer valueOf(int i) {
if (i >= IntegerCache.low && i <= IntegerCache.high)
return IntegerCache.cache[i + (-IntegerCache.low)];
return new Integer(i);
}
其实看注释我们也能看到,这个方法会缓存范围为-128到127的值,也就是如果i的值在此范围,将会从定义好的Integer对象数组中返回Integer对象,这样做可以减少对象的频繁创建。
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 =
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() {}
}
可以清楚的看到Integer缓存数组cache[]的创建过程,预先创建并添加了-128到127范围内的Integer对象。因为3在缓存值范围内,c和d拿到的是缓存中相同的对象,而e和f因为值超出了范围,各自拿到的是新创建的对象,对象之间的“==”号比较的是地址,自然一个是true,一个是false。
下面分析第三四句打印:
Integer a = 1;
Integer b = 2;
Integer c = 3;
System.out.println(c == (a + b));//true
System.out.println(c.equals(a + b));//true
虽然这两句打印结果相同,但是操作方式是不一样的。我们看这两句打印的反编译class指令:
Code:
80: getstatic #6 // Field java/lang/System.out:Ljava/io/PrintStream;
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;
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
第一句打印,根据84、88、92、95行我们看到,a、b、c都进行了拆箱操作,a和b拆箱后进行相加操作,跟拆箱后的c进行比较,结果自然是true。
第二句打印,根据112、116和119行,a和b分别拆箱然后相加,然后根据120行,相加后的值调用了Integer.valueOf()进行装箱。然后123行,c调用Integer的equals()方法进行比较。OK,那我们看一下这个方法的实现:
public boolean equals(Object obj) {
if (obj instanceof Integer) {
return value == ((Integer)obj).intValue();
}
return false;
}
参数为object,如果类型同是Integer,则比较两者的基础值。否则返回false。
那第二句打印时true也就没有异议了。
下面分析打印第五六句:
Integer a = 1;
Integer b = 2;
Long g = 3L;
System.out.println(g == (a + b));//true
System.out.println(g.equals(a + b));//false
同样贴出两句打印语句的反编译指令:
129: getstatic #6 // Field java/lang/System.out:Ljava/io/PrintStream;
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
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;
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
第一句打印,根据134、138、142、145,g拆箱后入栈,然后a和b拆箱进行相加,然后跟g进行比较,值相同,结果为true。
第二句打印,a和b拆箱后相加,然后对结果3进行装箱,然后Long类型的g调用Long.equals(Object obj)方法与该结果进行比较,我们看一下Long.equals()方法的实现:
public boolean equals(Object obj) {
if (obj instanceof Long) {
return value == ((Long)obj).longValue();
}
return false;
}
实现方式跟Integer如出一辙,如果比较对象是Long类型,则对基础值进行比较,否则返回false。这里因为结果3是Integer类型,所以是false。
分析完了上面那些,不知大家是否和我一样还有一些疑问?比如下面:
Integer c = 3;
int h = 3;
System.out.println(c == h);
System.out.println(c.equals(h));
我们可能都知道这两个打印结果都是true,但是他们背后又是怎么实现的呢?c和h比较,到底是装箱后的equals比较,还是拆箱后的值比较呢?Integer.equals(Object)这个方法参数类型是object,而我们传入的是基本数据类型int,这又是怎么处理的呢?
篇幅原因这里就不贴反编译码了,直接给出结论:第一句打印中,是对c拆箱后比较的。第二句中,会对h进行自动状态,然后通过equals(Object)方法比较。
总结
根据以上分析,我们可以得出一些结论:
- 如果是包装类之间的“==”运算,在没有算数运算的情况下不会自动拆箱,比较的是对象地址。遇到算数运算时会进行自动拆箱,比较数值。
- 如果是包装类和基本类型之间的"=="比较,包装类会拆箱后比较
- 包装类的equals(Object)方法是不处理数据转型关系的,如Long.equals(Integer.value(3)),类型不一致就会返回false。