Java中 "==" 和 equals 的区别 拓展Integer及IntegerCache缓冲池的介绍
话说,关于”==” equals的比较网上已经有很多大神总结了,但我在查找博文时,仍感觉有些支离破碎,有不能一篇概全的不爽,而大神们都有写的很味道的部分,所以本文主要是引用别人好的文章作出”最全”总结。
当然,我的总结肯定会在我的认知的基础上进行的,所以此文只是我个人当下认知下而产生,可能对于当下level和我不同的猿们,看法呢也会不同。 另外,本文大部分都是引用别人的代码和文字,本人只起到穿插连接内容的作用,但还是不要脸的挂了”原创”的牌子,希望不要”遭天谴”~~~~
好了,我还是那个和你一样,渴望成为大神的low级程序员,下面开始正文。
“==”
- 对象之间比较时,比较引用对象地址值 (String类型比较特殊 详见 例1)。
- 基本类型比较时,比较值。 int a=10 与 long b=10L 与 double c=10.0 比时值相同
(包装类参与比较值时,因为有自动装箱和拆箱,比较特殊 详见 例2 )
equals
- 对象之间比较,比较引用对象地址值 此时功能和对象间用“==”对比相同。(为什么相同呢?原理 例3)
- 特殊类,如String之间比较时,比较字符串内容(原理 详见 例4)
例1
借助内存分析来对比String中 “==”在不同情况下的作用
例1 转自 https://blog.csdn.net/uskystars/article/details/34075811
1. String str1 = "abc";
System.out.println(str1 == "abc");
步骤:
1) 栈中开辟一块空间存放引用str1,
2) String池中开辟一块空间,存放String常量"abc",
3) 引用str1指向池中String常量"abc",
4) str1所指代的地址即常量"abc"所在地址,输出为true
2. String str2 = new String("abc");
System.out.println(str2 == "abc");
步骤:
1) 栈中开辟一块空间存放引用str2,
2) 堆中开辟一块空间存放一个新建的String对象"abc",
3) 引用str2指向堆中的新建的String对象"abc",
4) str2所指代的对象地址为堆中地址,而常量"abc"地址在池中,输出为fals<font color="blue"> 例2 e
3. String str3 = new String("abc");
System.out.println(str3 == str2);
步骤:
1) 栈中开辟一块空间存放引用str3,
2) 堆中开辟一块新空间存放另外一个(不同于str2所指)新建的String对象,
3) 引用str3指向另外新建的那个String对象
4) str3和str2指向堆中不同的String对象,地址也不相同,输出为false
4. String str4 = "a" + "b";
System.out.println(str4 == "ab");
步骤:
1) 栈中开辟一块空间存放引用str4,
2) 根据编译器合并已知量的优化功能,池中开辟一块空间,存放合并后的String常量"ab",
3) 引用str4指向池中常量"ab",
4) str4所指即池中常量"ab",输出为true
5. final String s = "a";
String str5 = s + "b";
System.out.println(str5 == "ab");
步骤:
同4
6. String s1 = "a";
String s2 = "b";
String str6 = s1 + s2;
System.out.println(str6 == "ab");
步骤:
1) 栈中开辟一块中间存放引用s1,s1指向池中String常量"a",
2) 栈中开辟一块中间存放引用s2,s2指向池中String常量"b",
3) 栈中开辟一块中间存放引用str6,
4) s1 + s2通过StringBuilder的最后一步toString()方法还原一个新的String对象"ab",因此堆中开辟一块空间存放此对象,
5) 引用str6指向堆中(s1 + s2)所还原的新String对象,
6) str6指向的对象在堆中,而常量"ab"在池中,输出为false
7. String str7 = "abc".substring(0, 2);
步骤:
1) 栈中开辟一块空间存放引用str7,
2) substring()方法还原一个新的String对象"ab"(不同于str6所指),堆中开辟一块空间存放此对象,
3) 引用str7指向堆中的新String对象,
8. String str8 = "abc".toUpperCase();
步骤:
1) 栈中开辟一块空间存放引用str6,
2) toUpperCase()方法还原一个新的String对象"ABC",池中并未开辟新的空间存放String常量"ABC",
3) 引用str8指向堆中的新String对象
例2
转自 https://blog.csdn.net/qq_31459039/article/details/79714738
情况一:byte, short, int, long四种基本数据类型以及其包装类的比较:
int i =50;
Integer i1 =50;
Integer i2 =50;
Integer i3 = new Integer(50);
Integer i4 = new Integer(50);
Integer i5 = 300;
Integer i6 = 300;
System.out.println(i == i1);// true;i1自动拆箱变成基本类型,两基本类型比较值
System.out.println(i == i3);// true; i3自动拆箱变成基本类型,两基本类型比较值
System.out.println(i1 == i2);// true; i1和i3都指向常量池中同一个地址
System.out.println(i1 == i3);// false; 两个不同的对象
System.out.println(i3 == i4);// false; 两个不同的对象
System.out.println(i5 == i6);// false; 自动装箱时,如果值不在-128到127,就会创建一个新的对象
小结:
1.基本数据类型与其对应的包装类运算或比较时,会自动拆箱成基本数据类型;
2.在自动装装箱时,会先检查其值是否在-128到127之间,如果在这之间,就会直接指向常量池中其值的地址;
3.只要是new得到的一定是对象,存在堆内存中;
4.同时byte, short, long也具有该特性。
原因:JVM做的一些一些优化,将常用的基本数据类型在程序运行时就创建加载在常量池中。
情况二 : double, float类型的不同
Float f1 = 100f;
Float f2 = 100f;
Float f3 = 300f;
Float f4 = 300f;
System.out.println(f1 == f2);// false
System.out.println(f3 == f4);// false
小结 : float,double类型的包装类,都会在堆中创建一个新对象,因此比较的是对象的地址
拓展 : 谨慎包装类型的大小比较
基本数据类型比较大小木有问题,不过其对应的包装类型大小比较就需要注意了。看如下代码:
public class Client {
public static void main(String[] args) {
Integer a = new Integer(100);
Integer b = new Integer(100);
/* compareTo返回值:若a>b则返回1;若a==b则返回0;若a<b则返回-1 */
int result = a.compareTo(b);
System.out.println(result);
System.out.println(a > b);
System.out.println(a == b);
}
}
运行结果:
0
false
false
为什么(a==b)返回值会是false呢?
通过对比字符串比较来理解,基本类型100通过包装类Integer包装后生产一个Integer对象的引用a,
而“==”使用来判断两个操作数是否有相等关系。如果是基本类型就直接判断其值是否相等。
若是对象就判断是否是同一个对象的引用,显然我们new了两个不同的对象。
但注意:
对于”<”,”>” 只是用来判断两个基本类型的数值的大小关系。在进行(a
[java] view plain copy
public static Integer valueOf(int i) {
assert IntegerCache.high >= 127;
if (i >= IntegerCache.low && i <= IntegerCache.high)
return IntegerCache.cache[i + (-IntegerCache.low)];
return new Integer(i);
}
看一下源码大家都会明白,对于-128到127之间的数,会进行缓存,Integer i5 = 127时,会将127进行缓存,下次再写Integer i6 = 127时,就会直接从缓存中取,就不会new了
2.两个基本类型int进行相等比较,直接用==即可。
3.一个基本类型int和一个包装类型Integer比较,用==也可,比较时候,Integer类型做了拆箱操作。
4.Integer类型比较大小,要么调用Integer.intValue()转为基本类型用“==”比较,要么直接用equals比较。
①无论如何,Integer与new Integer不会相等。不会经历拆箱过程,i7的引用指向堆,而new Integer()指向专门存放他的内存(常量池),他们的内存地址不一样,所以为false(如L24)。
②两个都是非new出来的Integer,如果数在-128到127之间,则是true(如L18),否则为false(如L18)。java在编译Integer i2 = 128的时候,被翻译成-> Integer i2 = Integer.valueOf(128);而valueOf()函数会对-128到127之间的数进行缓存。
③两个都是new出来的,都为false(如L27)。
④int和integer(无论new否)比,都为true,因为会把Integer自动拆箱为int再去比(如L13、L14)
拓展 Integer及Integer缓冲池的介绍
转自 https://www.cnblogs.com/timecloud/p/6555360.html
Integer中有个静态内部类IntegerCache,里面有个cache[],也就是Integer常量池,常量池的大小为一个字节(-128~127)。
源码为(jdk1.8.0_101)
1 private static class IntegerCache {
2 static final int low = -128;
3 static final int high;
4 static final Integer cache[];
5
6 static {
7 // high value may be configured by property
8 int h = 127;
9 String integerCacheHighPropValue =
10 sun.misc.VM.getSavedProperty("java.lang.Integer.IntegerCache.high");
11 if (integerCacheHighPropValue != null) {
12 try {
13 int i = parseInt(integerCacheHighPropValue);
14 i = Math.max(i, 127);
15 // Maximum array size is Integer.MAX_VALUE
16 h = Math.min(i, Integer.MAX_VALUE - (-low) -1);
17 } catch( NumberFormatException nfe) {
18 // If the property cannot be parsed into an int, ignore it.
19 }
20 }
21 high = h;
22
23 cache = new Integer[(high - low) + 1];
24 int j = low;
25 for(int k = 0; k < cache.length; k++)
26 cache[k] = new Integer(j++);
27
28 // range [-128, 127] must be interned (JLS7 5.1.7)
29 assert IntegerCache.high >= 127;
30 }
31
32 private IntegerCache() {}
33 }
当创建Integer对象时,不使用new Integer(int i)语句,大小在-128~127之间,对象存放在Integer常量池中。
例如:Integer a = 10;
调用的是Integer.valueOf()方法,代码为
1 public static Integer valueOf(int i) {
2 if (i >= IntegerCache.low && i <= IntegerCache.high)
3 return IntegerCache.cache[i + (-IntegerCache.low)];
4 return new Integer(i);
5 }
这也是自动装箱的代码实现。
测试Integer的特性:
1 public class TestInteger {
2 public static void main(String[] args) {
3 //jdk1.5后 虚拟机为包装类提供了缓冲池,Integer缓冲池的大小为一个字节(-128~127);
4 //创建 1 个对象,存放在常量池中。引用c1,c2存放在栈内存中。
5 Integer c1 = 1;
6 Integer c2 = 1;
7 System.out.println("c1 = c2 ? " + (c1 == c2)); //true
8
9 //创建 2 个对象,存放在堆内存中。2 个引用存放在栈内存中。
10 Integer b1 = 130; //130不在(-128~127)之间
11 Integer b2 = 130;
12 System.out.println("b1 = b2 ? " + (b1 == b2)); //false
13
14 //创建2个对象,存放在堆内存中。
15 Integer b3 = new Integer(2);
16 Integer b4 = new Integer(2);
17 System.out.println("b3 = b4 ? " + (b3 == b4)); //false
18 //下面两行代码证明了使用new Integer(int i) (i 在-128~127之间)创建对象不会保存在常量池中。
19 Integer b5 = 2;
20 System.out.println("b3 = b5 ? " + (b3 == b5)); //false
21
22 //Integer的自动拆箱,b3自动转换成数字 2。
23 System.out.println("b3 = 2 ? " + (b3 == 2)); //true
24 Integer b6 = 210;
25 System.out.println("b6 = 210 ? " + (b6 == 210)); //true
26 }
27 }
打印结果为:
c1 = c2 ? true
b1 = b2 ? false
b3 = b4 ? false
b3 = b5 ? false
b3 = 2 ? true
b6 = 210 ? true
关于Interger自动装箱和拆箱,可以如下理解
例3
equals方法最初是在所有类的基类Object中进行定义的,源码是
public boolean equals(Object obj) {
return (this == obj);
}
由equals的源码可以看出这里定义的equals与==是等效的,都是比较的this,也就是引用对象地址值 。而大部分JDK自带的类以及自定义的类,都没有重写Object中的equals方法,而所以的类都继承于Object,调用equals方法时,其实使用的是父类Object中的方法。
例4
转自 https://www.cnblogs.com/Eason-S/p/5524837.html
String类对equals方法的重写,进而使方法对比的是字符串内容
1 public boolean equals(Object anObject) {
2 if (this == anObject) {
3 return true;
4 }
5 if (anObject instanceof String) {
6 String anotherString = (String)anObject;
7 int n = count;
8 if (n == anotherString.count) {
9 char v1[] = value;
10 char v2[] = anotherString.value;
11 int i = offset;
12 int j = anotherString.offset;
13 while (n-- != 0) {
14 if (v1[i++] != v2[j++])
15 return false;
16 }
17 return true;
18 }
19 }
20 return false;
21 }
对equals重新需要注意五点:
1 自反性:对任意引用值X,x.equals(x)的返回值一定为true;
2 对称性:对于任何引用值x,y,当且仅当y.equals(x)返回值为true时,x.equals(y)的返回值一定为true;
3 传递性:如果x.equals(y)=true, y.equals(z)=true,则x.equals(z)=true ;
4 一致性:如果参与比较的对象没任何改变,则对象比较的结果也不应该有任何改变;
5 非空性:任何非空的引用值X,x.equals(null)的返回值一定为false
关于重写equals方法、equals其它重点介绍以及equals方法与hashCode方法渊源,可查看本人转载文章 https://blog.csdn.net/ted_cs/article/details/82700972
End!
猿们啊,整理不易啊,如果感觉有帮到你的话,点赞,评论,关注给我来个大保健啊 ~~~