首先,我锤5月份要开发布会了,在这祝我锤产品大卖。好了,言归正传。最近从上家公司辞职了,在家休息了一小段时间,然后也跑了一些面试,发现多家公司的笔试题中都有Integer和String,然后各种情况下用 ==,或者equals方法去比较,问是否相等。自己发现有些情况下,答题的时候也是连猜带蒙的。刚好最近有时间,人一闲下来呀,还是总得干点什么。所以,就准备好好研究下Integer,String在各种情况下用==,equals去比较,
第一,究竟是在比较什么?
第二,每一步是怎么进行的?
希望这篇文章能够很好的回答上述的两个问题。
我们先来看下Integer类在各种情况下的 ==,equals在比较什么:
代码如下图:
大家可以试着写下自己的判断结果,如果你的结果和运行结果不一致,那你就需要好好看下下面的解析了。如果你的结果和运行结果一致,你也可以看下解析,看你的判断思路是和和下面一致。
System.out.println(i == j); // true
System.out.println(is == js); // false
System.out.println(i == d); // false
System.out.println(i.equals(d)); // true
System.out.println(i == 2); // true
System.out.println(d == 2); // true
System.out.println(2L == g); // true
System.out.println(i.equals(g)); // false
System.out.println(g.equals(2)); // false
众所周知的原则是:
(1) == 比较的是两个对象的内存地址
(2) equals方法没有重写的话是和 == 一样的,比较的是两个对象的地址,如果类重写了equals方法,比较的就是两个对象的内容。(像 Integer,String 等类,都重写了equals方法)
这两条原则大体上是没错的,但是有些情况下,我们用两条原则去判断,有时候有点捉摸不定,有时候出现判断失误了。比如说,Integer i = 2,虚拟机究竟做了什么? is == js 为什么返回false ? 要解释第一个问题,我们需要 用 javap -c -l IntegerTest.class,反编译IntegerTest,拿到字节码,看下虚拟机是怎么执行这段代码的。IntegerTest字节码如下:
public static void main(java.lang.String[]);
Code:
0: iconst_2 // 将 int 型 2 推送至栈顶
// 调用静态方法
1: invokestatic #2 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
4: astore_1 // 将栈顶引用型数值存入第二个本地变量
5: iconst_2
6: invokestatic #2 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
9: astore_2
// 创建一个对象,并将其引用值压入栈顶
10: new #3 // class java/lang/Integer
13: dup // 复制栈顶数值并将复制值压入栈顶
14: iconst_2 // 将 int 型 2 推送至栈顶
// 调用超类构造方法,实例初始化方法,私有方法
15: invokespecial #4 // Method java/lang/Integer."
18: astore_3 // 将栈顶引用型数值存入第四个本地变量
19: new #3 // class java/lang/Integer
22: dup
23: sipush 200 // 将一个 短整型(-32768~32767)常量值推送至栈顶
26: invokespecial #4 // Method java/lang/Integer."
29: astore 4 // 将栈顶引用型数值存入指定本地变量
31: sipush 200
34: invokestatic #2 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
37: astore 5
39: sipush 200
42: invokestatic #2 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
45: astore 6
47: new #5 // class java/lang/Long
50: dup
// 将 long 或 double 型 常量值从常量池中推送至栈顶
51: ldc2_w #6 // long 2l
54: invokespecial #8 // Method java/lang/Long."
57: astore 7
59: getstatic #9 // Field java/lang/System.out:Ljava/io/PrintStream;
62: aload_1
63: aload_2
64: if_acmpne 71 // 比较栈顶两引用型数值,当结果不相等时跳转
67: iconst_1
68: goto 72
71: iconst_0
72: invokevirtual #10 // Method java/io/PrintStream.println:(Z)V
75: getstatic #9 // Field java/lang/System.out:Ljava/io/PrintStream;
78: aload 5
80: aload 6
82: if_acmpne 89
85: iconst_1
86: goto 90
89: iconst_0
90: invokevirtual #10 // Method java/io/PrintStream.println:(Z)V
93: getstatic #9 // Field java/lang/System.out:Ljava/io/PrintStream;
96: aload_1
97: aload_3
98: if_acmpne 105
101: iconst_1
102: goto 106
105: iconst_0
106: invokevirtual #10 // Method java/io/PrintStream.println:(Z)V
109: getstatic #9 // Field java/lang/System.out:Ljava/io/PrintStream;
112: aload_1
113: aload_3
114: invokevirtual #11 // Method java/lang/Integer.equals:(Ljava/lang/Object;)Z
117: invokevirtual #10 // Method java/io/PrintStream.println:(Z)V
120: getstatic #9 // Field java/lang/System.out:Ljava/io/PrintStream;
123: aload_1
124: invokevirtual #12 // Method java/lang/Integer.intValue:()I
127: iconst_2
128: if_icmpne 135
131: iconst_1
132: goto 136
135: iconst_0
136: invokevirtual #10 // Method java/io/PrintStream.println:(Z)V
139: getstatic #9 // Field java/lang/System.out:Ljava/io/PrintStream;
142: aload_3
143: invokevirtual #12 // Method java/lang/Integer.intValue:()I
146: iconst_2
147: if_icmpne 154
150: iconst_1
151: goto 155
154: iconst_0
155: invokevirtual #10 // Method java/io/PrintStream.println:(Z)V
158: getstatic #9 // Field java/lang/System.out:Ljava/io/PrintStream;
161: ldc2_w #6 // long 2l
164: aload 7
166: invokevirtual #13 // Method java/lang/Long.longValue:()J
169: lcmp
170: ifne 177
173: iconst_1
174: goto 178
177: iconst_0
178: invokevirtual #10 // Method java/io/PrintStream.println:(Z)V
181: getstatic #9 // Field java/lang/System.out:Ljava/io/PrintStream;
184: aload_1
185: aload 7
187: invokevirtual #11 // Method java/lang/Integer.equals:(Ljava/lang/Object;)Z
190: invokevirtual #10 // Method java/io/PrintStream.println:(Z)V
193: getstatic #9 // Field java/lang/System.out:Ljava/io/PrintStream;
196: aload 7
198: iconst_2
199: invokestatic #2 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
202: invokevirtual #14 // Method java/lang/Long.equals:(Ljava/lang/Object;)Z
205: invokevirtual #10 // Method java/io/PrintStream.println:(Z)V
208: getstatic #9 // Field java/lang/System.out:Ljava/io/PrintStream;
211: new #15 // class java/lang/StringBuilder
214: dup
215: invokespecial #16 // Method java/lang/StringBuilder."
218: ldc #17 // String d =
220: invokevirtual #18 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
223: aload_3
224: invokestatic #19 // Method java/lang/System.identityHashCode:(Ljava/lang/Object;)I
227: invokevirtual #20 // Method java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder;
230: ldc #21 // String value_2 =
232: invokevirtual #18 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
235: iconst_2
236: invokestatic #2 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
239: invokestatic #19 // Method java/lang/System.identityHashCode:(Ljava/lang/Object;)I
242: invokevirtual #20 // Method java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder;
245: invokevirtual #22 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
248: invokevirtual #23 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
251: return
LineNumberTable:
// 代码中的行数 :对应的在字节码中的行数
line 7: 0
line 8: 5
line 9: 10
line 11: 19
line 12: 31
line 13: 39
line 15: 47
line 17: 59
line 18: 75
line 20: 93
line 21: 109
line 23: 120
line 24: 139
line 26: 158
line 27: 181
line 28: 193
line 30: 208
line 31: 224
line 33: 236
line 30: 248
line 35: 251
}
Integer i = 2 这句代码在 字节码里对应以下几个指令:
0: iconst_2 // 将 int 型 2 推送至栈顶
// 调用静态方法
1: invokestatic #2 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
4: astore_1 // 将栈顶引用型数值存入第二个本地变量
通过字节码可以很清晰的看出,Integer i = 2, 是通过将 2 通过 Integer.valuesOf方法生成一个Integer对象,再将地址赋给 i;
那么我们来看看System.out.println( i == j ) 为什么返回 true, 以及
System.out.println( is == js ) 为什么返回 false ;
首先,我们看看Integer.valueOf是怎么构造一个Integer并返回的。
Integer.valueOf 的 源码:
再打开IntegerCache的代码:
这两段代码的意思是,如果 Integer.valueOf( int q ) 方法传入的值如果是在 -128~127之间的话,那么返回的Integer是被 Integer cache [] 数组 缓存起来的。cache数组存的是Integer的引用。所以,
system.out.println( i = j ) i 和 j 的内存地址是一样的。因Integer类重写了equals和hashCode 方法,但我们可以通过System.identityHashCode(Object)来打印出 i 和 j 对象的内存地址;
i 和 j 的 内存地址如下:
我们能看到, i 和 j 的内存地址是一致的。
通过这张图,可以得知,System.out.println( is == js ) 是 false,因为是两个不同的对象。
System.out.println( i = d) 肯定是 false ,因为是不同的对象。
System.out.println( i.equals(d) ) true,因为Integer重写了equals方法,比较的是对象的intValue。
那么以下两个 == 比较,返回的也是true ? 他们和 2 都是同一个对象么?
System.out.println(i == 2); true
System.out.println(d == 2); true
其中 i 和 2 是同一个对象,而 d 和 2 并不是同一个对象,我们可以打印出内存地址信息:
可以看到打印出来的内存地址, i 和 2 是相同的 , 而 2 和 d 是不同的。如果值超过了 -128~127这个范围的话,那么, i , 2 ,d 内存地址都不同:
既然内存不同,,这为什么返回true,我们可以看下字节码的内容:
System.out.println( i == 2 ) 在代码中是 23行,对应字节码中的120行
line 23: 120
字节码内容如下:
123行中 加载 i ,
124行,调用了i 的实例方法Integer,intValue(),
127行,将常量2推向栈顶,
128行,比较栈顶两个int型数值的大小。就变成了——> 2 == 2 当然是true了。
System.out.println( d == 2 ) 也是一样。
System.out.println( 2L== g ) 同上。
接下来,我们看下:
System.out.println(i.equals(g)); // false
看下 Integer类 重写的equals方法代码:
System.out.println(g.equals(2)); // false
可以看下这段的字节码:
再看Long类重写的equals方法代码,变成了 g .equals(Integer.valueOf(2)),那结果就是false了。
Integer类 各种情况下 == 比较总结:
1) Integer类的一些 == 比较,要注意到自动装箱机制的存在,(-128~127)这个范围之间,如果是通过 == 赋值的话,是返回IntegerCache[ ] 数组中的引用。超过这个范围,则返回一个新的 Integer对象。
2)new Integer 和 数值( 比如说 2 比较的时候 ),分析字节码得知,new Integer对象在比较的时候调用到了intValue()方法,其实比较的是数值是否相等。
各种情况下 equals 比较总结:
1) 每个对象重写 equals()方法,对象的equals方法是用来比较同类型的两个对象内容是否相等的。而Integer.equals( Long ),肯定是不相等的。
我们再来看下String类在各种情况下的 ==,equals在比较什么:
代码如下图:
运行结果:
System.out.println( flower1 == flower2 ); // true
System.out.println( "big flower" == flower1); // true
System.out.println( flower1 == flower3 ); // false
System.out.println( flower1 == flower3 ); // false 这个 返回结果是false就不用解析了,因为是两个不同的对象了。
System.out.println( flower1 == flower2 ); // true
System.out.println( "big flower" == flower1); // true
这两个为true的话,从代码表面看不出什么,我们输出它的字节码文件来分析下吧。
字节码文件如下:
public static void main(java.lang.String[]);
Code:
// 将int、float、或String型常量值从常量池中推送至栈顶
0: ldc #2 // String big flower
2: astore_1 // 将栈顶引用型数值存入第二个本地变量
3: ldc #2 // String big flower
5: astore_2
// 创建一个对象,并将其引用值压入栈顶
6: new #3 // class java/lang/String
9: dup // 复制栈顶数值并将复制值压入栈顶
10: ldc #2 // String big flower
// 调用超类构造方法,实例初始化方法,私有方法
12: invokespecial #4 // Method java/lang/String."
15: astore_3 // 将栈顶引用型数值存入第四个本地变量
// 获取指定类的静态域,并将其值压入栈顶
16: getstatic #5 // Field java/lang/System.out:Ljava/io/PrintStream;
19: aload_1 // 将第二个引用类型本地变量推送至栈顶
20: aload_2 // 将第三个引用类型本地变量推送至栈顶
21: if_acmpne 28 // 比较栈顶两引用型数值,当结果不相等时跳转
24: iconst_1 // 将 int 型 1 推送至栈顶
25: goto 29
28: iconst_0 // 将 int 型 0 推送至栈顶
// 调用实例方法
29: invokevirtual #6 // Method java/io/PrintStream.println:(Z)V
32: getstatic #5 // Field java/lang/System.out:Ljava/io/PrintStream;
35: ldc #2 // String big flower
37: aload_1
38: if_acmpne 45
41: iconst_1
42: goto 46
45: iconst_0
46: invokevirtual #6 // Method java/io/PrintStream.println:(Z)V
49: getstatic #5 // Field java/lang/System.out:Ljava/io/PrintStream;
52: aload_1
53: aload_3
54: if_acmpne 61
57: iconst_1
58: goto 62
61: iconst_0
62: invokevirtual #6 // Method java/io/PrintStream.println:(Z)V
65: return
LineNumberTable:
line 7: 0
line 8: 3
line 9: 6
line 11: 16
line 12: 32
line 13: 49
line 15: 65
}
首先看下 System.out.println( flower1 == flower2 ) 对应的字节码:
首先是 aload_1( 对应的值是flower1 ) 再执行 aload_2( 对应 flower2 ),然后对这两个引用型的值进行比较。那我们就只能看下 aload1 和 aload2 ,存进去的时候是存了什么。字节码如下:
发现 astore1 和 astore2 存的都是从常量池中拿出 big flower 这个 String,这就是说 flower1和flower2其实内存地址是一样的。我们可以通过java.lang.System.identityHashCode(Object) 打印出flower1和flower2的内存地址:
通过观察字节码,发现 System.out.println( "big flower" == flower1); 比较的也是从常量池中取的 big flower 这个字符串,所以是相等的:
我们可以打印出内存地址:
关于String类的设计,其实是应用了 享元设计模式。什么是享元设计模式呢?就是采用一个共享来避免大量拥有相同内容对象的开销。在String中的体现就是,使用 = 赋值的时候,如果常量池中已经有了此字符串,那就直接引用。而通过new String(" big flower")去创建的时候,因为创建了一个新的String对象,在堆中分配了内存,所以每次内存地址都不一样。但是通过new创建的话,字符串内容如果常量池有了的话,还是从常量池中加载哦。如果常量池中没有的话,就创建一个放入常量池。
运用上面所了解到的字节码知识,可以分析下 代码为什么返回 false :
String flower1 = "big flower1";
String flower2 = "big flower1";
String d = "big flower1"+"big flower1";
String s = flower1 + flower2;
System.out.println(s == d); // false;
Integer类 各种情况下 == 比较总结:
1)使用 == 比较两个字符串的时候,要注意到常量池的存在,如果是比较
String flower1 = "big flower";
String flower2 = "big flower";
或者: “big flower”,
当这三个其实都是从常量池中取到的对于 big flower的引用,所以,都是同一个对象。
2) 只要是 赋值的时候,有 new String(" big flower"); 那么,使用 == 去比较,一定是返回false,永远都是不同的对象。只能通过equals去比较两个对象的内容;
3) 拼接类的String对象比较:
String d = "big flower1"+"big flower1"; 在字节码层面就相当与 String d =big flower1big flower1; ( 字节码 6-8 行 )
String s = flower1 + flower2; 这个 在字节码中看到的却是调用到了 StringBuilder去拼接的;( 字节码 9-24 行 )
字节码如下:
Integer和String类中的 各种 ==,equals()的比较分析就到这了。如果你觉得对你有帮助,欢迎付费:
【1】 Java虚拟机字节码指令表
【2】 Java虚拟机字节码指令表分类
【3】 Java常量池(1)
【4】 Java常量池(2)
【5】String类的享元模式