欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Java中 "==" 和 equals 的区别 拓展Integer及IntegerCache缓冲池的介绍

程序员文章站 2022-04-15 23:44:35
...

话说,关于”==” 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自动装箱和拆箱,可以如下理解
Java中 "==" 和 equals 的区别 拓展Integer及IntegerCache缓冲池的介绍


例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!

猿们啊,整理不易啊,如果感觉有帮到你的话,点赞,评论,关注给我来个大保健啊 ~~~



Java中 "==" 和 equals 的区别 拓展Integer及IntegerCache缓冲池的介绍

相关标签: java equals ==