相信我们对地址值都不陌生,我还记得初学计算机时C语言的指针便将我折磨的死去活来。而哈希值和地址值是不一样的,它们虽然也有关系,下面讲一下。
地址值:内存地址,变量在内存中的位置。这里的地址是指JVM虚拟出来的内存地址,不是实际物理内存地址。
哈希值:对于哈希值应该分两种情况,一种是没有重写hashcode()
方法,也即是默认使用超类Object里的int hashcode()
,这里返回的值与地址值有一定关系,有时候可能会相等,有时候可能会不等,具体取决于运行时库和JVM的具体实现,我们可以看成是一个逻辑地址。
另一种就是重写了hashcode()
方法,这里就是利用到了各种算法得到的哈希值,比如String类、包装类等都重写了hashcode()
方法,我们也可以对我们自己写的类重写这个方法,使得它返回一个我们要的值,甚至一直返回一个值都可以,只要我们想要即可。
注意,对于第一种没有重写hashcode()
方法得到的哈希值,因为与地址值有关,所以得到的哈希值是不会相等的。而对于重写了hashcode()
方法得到的哈希值,由于算法的缘故,所以是可能存在相等的,这个也叫做哈希碰撞。比如以下,输出的两个哈希值结果相同。
System.out.println("重地".hashcode());
System.out.println("通话".hashcode());
对于哈希值,不同的重写方法得到的哈希值不同,以下讲一下具体的实现:
hashcode()
,得到的是一个逻辑地址,只要不是同一个对象,哈希值就不同,且永不重复。 //结果不同
Object obj1 = new Object();
Object obj2 = new Object();
System.out.println("obj1哈希值:"+ obj1.hashCode());
System.out.println("obj2哈希值:"+ obj2.hashCode());
//结果一样
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());
//结果一样,都是100
Integer int1 = new Integer(100);
Integer int2 = new Integer(100);
System.out.println("包装类型int1哈希值:"+int1.hashCode());
System.out.println("包装类型int2哈希值:"+int2.hashCode());
我们都知道Object里有equal方法,其实equal从本质上来说就是一个==,这个我们可以从Object的源码中得到。
所以当我们讨论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,比较的是数值
==的作用其实也有两个,对于普通类型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
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 | 无缓存 |
上面String输出的结果就是利用到了String常量池,对于String我们也多种创建方法,但对于最直接的方法即使用双引号然后赋值的String存在于常量池中,而我们使用new得到的String就不在常量池中了。
上面图中的str1 和 str2都是直接使用等号赋值的,所以第一次对于str1赋值时就会把”abc“存到常量池中,当str2使用等号赋值时,它会先在常量池找是否存在”abc“,存在即将这个赋给str2,所以它们其实是同一个对象。
对于str3,由于它是new出来的,所以它与常量池就没什么关系了,与str1 str2对象不一样,所以==的结果也是false。
常量池其实并没有我讲的这么简单,大家有兴趣还需深入了解下。下面是关于常量池的视频,比较基础,如果大家看不懂我说的,也可以看下下面视频:
https://www.bilibili.com/video/BV1gJ411k7EM?p=135