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

版权声明:Hi,屏幕前的猿你好,我和你一样是一个渴望成为技术大牛的low级程序员,(当然如果是大神的话,怎么会来到我分享编程基础知识的博客呢),此篇博文中分享内容如帮到你,请一定不能吝啬帮我点赞,评论,关注,帮我来个全套大保健呢!我愿与你一同成长变强,得到技术加持,祈愿! https://blog.csdn.net/ted_cs/article/details/82657765

话说,关于”==” 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!

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



这里写图片描述

猜你喜欢

转载自blog.csdn.net/ted_cs/article/details/82657765