文章目录

  • 1.知识引入:哈希值与地址值
  • 2.hashcode()
  • 3.equal
  • 4.==
  • 1)包装类的缓存
  • 2)String常量池



对于刚学Java的菜鸡来说,一定会对equal、==、hashCode这三个东西晕头转向,下面就来讲一下这三者的联系与区别。

1.知识引入:哈希值与地址值

相信我们对地址值都不陌生,我还记得初学计算机时C语言的指针便将我折磨的死去活来。而哈希值和地址值是不一样的,它们虽然也有关系,下面讲一下。

  • 地址值:内存地址,变量在内存中的位置。这里的地址是指JVM虚拟出来的内存地址,不是实际物理内存地址。
  • 哈希值:对于哈希值应该分两种情况,一种是没有重写hashcode()方法,也即是默认使用超类Object里的int hashcode()这里返回的值与地址值有一定关系,有时候可能会相等,有时候可能会不等,具体取决于运行时库和JVM的具体实现,我们可以看成是一个逻辑地址。
    另一种就是重写了hashcode()方法,这里就是利用到了各种算法得到的哈希值,比如String类、包装类等都重写了hashcode()方法,我们也可以对我们自己写的类重写这个方法,使得它返回一个我们要的值,甚至一直返回一个值都可以,只要我们想要即可。

注意,对于第一种没有重写hashcode()方法得到的哈希值,因为与地址值有关,所以得到的哈希值是不会相等的。而对于重写了hashcode()方法得到的哈希值,由于算法的缘故,所以是可能存在相等的,这个也叫做哈希碰撞。比如以下,输出的两个哈希值结果相同。

System.out.println("重地".hashcode());
System.out.println("通话".hashcode());

2.hashcode()

对于哈希值,不同的重写方法得到的哈希值不同,以下讲一下具体的实现:

  • Object:这种就是默认不重写hashcode(),得到的是一个逻辑地址,只要不是同一个对象,哈希值就不同,且永不重复。
//结果不同
		Object obj1 = new Object();
        Object obj2 = new Object();
        System.out.println("obj1哈希值:"+ obj1.hashCode());
        System.out.println("obj2哈希值:"+ obj2.hashCode());
  • String:根据String类包含的字符串的内容,根据一种特殊算法返回哈希码,只要字符串内容相同,返回的哈希码也相同。
//结果一样
 		String str1 = new String("123");
        String str2 = new String("123");
        String str3 = "123";
        System.out.println("str1哈希值:" + str1.hashCode());
        System.out.println("str2哈希值:" + str2.hashCode());
        System.out.println("str3哈希值:" + str3.hashCode());
  • 包装类:根据值得到哈希值,值相同的哈希值都一样,对于Integer它比较特殊,哈希值就是它的值。
//结果一样,都是100
 		Integer int1 = new Integer(100);
        Integer int2 = new Integer(100);
        System.out.println("包装类型int1哈希值:"+int1.hashCode());
        System.out.println("包装类型int2哈希值:"+int2.hashCode());

3.equal

我们都知道Object里有equal方法,其实equal从本质上来说就是一个==,这个我们可以从Object的源码中得到。

地址值转string java java中地址值是什么意思_缓存


所以当我们讨论equal的时候也要分两种情况,一种是默认的equal,一种是重写了的equal,第一种就是==,比较的是地址值;重写了,一般就是比较数值,比如String类和包装类,当然我们也可以有自己的逻辑,自定义equal。

//没有重写equal
Object obj1 = new Object();
Object obj2 = new Object();
System.out.println(obj1.equals(obj2));   //结果是false,比较的是地址值

//重写了equal
Integer a = 100;
Integer b = 100;
System.out.println(a.equals(b));   //结果是true,比较的是数值

4.==

==的作用其实也有两个,对于普通类型int、float等,==比较的是数值;对于引用类型,==比较的是地址值。

//引用类型
Object obj1 = new Object();
Object obj2 = new Object();
System.out.println(obj1 == obj2);  //结果是false

//普通类型
int a = 100;
int b = 100;
System.out.println(a == b);  //结果是true

但是当==用于String或者包装类时,大家要特别注意,这里涉及一个缓存的问题,大家看以下代码,是不是觉得突然看不懂了。这里其实涉及到一个缓存的问题。

String a = "123";
String b = "123";
String c = new String("123");
System.out.println(a == b);  //结果是true
System.out.println(a == c);  //结果是false

Integer a = 10;
Integer b = 10;
System.out.println(a == b); //结果是true

Integer c = 3000;
Integer d = 3000;
System.out.println(c == d); //结果是false
1)包装类的缓存

java内部为了节省内存,在包装类内部缓存了一些常用的值,比如Integer中有一个数组缓存了值从-128到127的Integer对象。当我们调用Integer.valueOf(int i){这一步自动装箱就会用到}的时候,如果i的值时结余-128到127之间的,会直接从这个缓存中返回一个对象,否则就new一个新的Integer对象。

所以这就是为什么我们上面a==b是true,c == d是false,因为ab数值在缓存区内,cd数值超过缓冲区,在缓冲区它们是同一个对象,超过缓冲区就是new一个新的对象。

包装类

缓冲值范围

Boolean

true,false(全部值)

Byte

-128~127(全部值)

Short

-128~127

Character

0~127

Integer

-128~127

Long

-128~127

Float

无缓存

Double

无缓存

2)String常量池

上面String输出的结果就是利用到了String常量池,对于String我们也多种创建方法,但对于最直接的方法即使用双引号然后赋值的String存在于常量池中,而我们使用new得到的String就不在常量池中了。

地址值转string java java中地址值是什么意思_缓存_02


上面图中的str1 和 str2都是直接使用等号赋值的,所以第一次对于str1赋值时就会把”abc“存到常量池中,当str2使用等号赋值时,它会先在常量池找是否存在”abc“,存在即将这个赋给str2,所以它们其实是同一个对象。

对于str3,由于它是new出来的,所以它与常量池就没什么关系了,与str1 str2对象不一样,所以==的结果也是false。

常量池其实并没有我讲的这么简单,大家有兴趣还需深入了解下。下面是关于常量池的视频,比较基础,如果大家看不懂我说的,也可以看下下面视频:

https://www.bilibili.com/video/BV1gJ411k7EM?p=135