问题引出
案例
在《深入理解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); //case1:true
System.out.println(e==f); //case2:false
System.out.println(c==(a+b)); //case3:true
System.out.println(c.equals(a+b)); //case4:true
System.out.println(g==(a+b)); //case5:true
System.out.println(g.equals(a+b)); //case6:false
}
在书中并没有给出运行结果和具体分析,给出了两点提示:
- == 在不遇到算术运算时不会自动装箱
- 他们的equal()方法不处理数据转型的关系
疑惑
初看运行结果,有着较多疑惑:
- 疑惑1: ==在Java中对于对象而言是比较对象的地址,为何case1中结果为true
- 疑惑2: case1与case2同为Integar类型的比较,为何case1结果为true,case2结果为false
- 疑惑3: c,g的值都为3,为何在使用equal()方法与a+b比较二者运行结果case4为true,case6为false
问题解决
疑惑1与疑惑2
实质上,疑惑1与疑惑2是相同的原因导致的,因此放在一起解答。
这里用一个最简单的小案例进行解释
public void testAutoBoxing() {
Integer i=1;
}
使用编译器的小伙伴们不妨在Integer类的 valueOf(int i)上打上断点。
在调试模式下运行上方的代码,会发现跳到Integer类的 valueOf(int i)上(为何会跳到此暂且不谈)
可以看出在创建Integar对象自动装箱时,并不是直接new一个新的Integer对象,而是先判断是否在cache范围内。若是:直接返回cache中的对象;若不是:创建新的Integer对象。
让我们来看一下上图中IntegerCache类的源码,就在valueOf(int i)方法的上方
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中储存了值为-128~high 个Integer对象,其中high可以由用户自行配置,默认为127。同理LongCache以及其他包装类也是如此。
这样一来,疑惑1和疑惑2就迎刃而解了:
- 疑惑1:由于c与d的值为3,都在-128到127的范围内,因此实质上都指向了IntegerCache中存储的同一个对象,地址自然相同
- 疑惑2:由于e和f的值为321,超出了IntegerCache的界限,因此为e和f分别创建了不同的Integer对象,地址自然不同
还记的刚才分析过程中遗留的问题吗?为何会i的赋值跳到Integer类的valueOf(int i)方法上
不妨深入testAutoBoxing()看看:
public void testAutoBoxing();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=1, locals=2, args_size=1
0: iconst_1
1: invokestatic #2 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
4: astore_1
5: return
LineNumberTable:
line 36: 0
line 37: 5
LocalVariableTable:
Start Length Slot Name Signature
0 6 0 this Lchapter_10/TestEqual;
5 1 1 i Ljava/lang/Integer;
RuntimeVisibleAnnotations:
0: #46()
在上面代码的第7行,为i赋值时,编译器为我们执行并不是Integer()构造器方法,而是valueOf()方法。 之所以这样做的原因,可以给出以下解释:
在-128~high范围内的Integer类型变量较常使用,因此用cache缓存这些变量,编译器为我们作了优化,装箱时调用valueOf()方法,在创建前先进行判断是否是在该范围内,是则将变量直接指向cache中缓存的Integer,这种优化在大型项目中起到了节省空间的作用。
疑惑3
- 疑惑3: c,g的值都为3,为何在使用equal()方法与a+b比较二者运行结果case4为true,case6为false
同样的,写一个简单的案例:
public void testLongAndIntegerEqual() {
Integer a=1;
Long b=1L;
System.out.println(b.equals(a));
}
看看Long类的equal()源码:
public boolean equals(Object obj) {
if (obj instanceof Long) {
return value == ((Long)obj).longValue();
}
return false;
}
可以看出,Long类中没有提供对其他包装类的转换,如果与其比较的不是同为Long类型的对象,直接返回false
总结
最后,我们再通过注释的方式解释下最初的案例
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); //case1:指向同一个cache对象,因此返回true
System.out.println(e==f); //case2:指向不同对象,因此返回false
System.out.println(c==(a+b)); //case3:在(a+b)运算中发生了自动拆箱与装箱,
// (a+b)与c指向对象相等,因此返回true
System.out.println(c.equals(a+b)); //case4:在(a+b)运算中发生了自动装箱,
// 装箱后的(a+b)与c指向相同对象,因此返回true
System.out.println(g==(a+b)); //case5:在(a+b)运算中发生了自动拆箱与装箱,
// (a+b)与g指向对象相等,因此返回true
System.out.println(g.equals(a+b)); //case6:在(a+b)运算中发生了自动装箱,转化为Integer类型
// 装箱后的(a+b)与c为不同类型对象,直接返回false
}