#记录
equals 和 ==
- equals 与 == 的区别
- String str1 = new String(“aaa”);
- String str2 = new String(“aaa”);
- System.out.println(str1.equals(str2));// 类覆盖了equals方法,返回为true
- equals()通过其它方式比较两个对象是否相等。通常的做法是:若两个对象的内容相等,则equals()方法返回true;否则,返回fasle
- System.out.println(str1 == str2);// 若类没有覆盖equals方法,等价于“==”比较对象 ==:它的作用是判断两个对象的地址是不是相等返回 false
重写equals
`public boolean equals(Object obj) {
if (obj != null) {
if (this == obj) {
return true;
}
if (obj instanceof Person) {
Person person = (Person) obj;
if (person.getKeyId() == this.getKeyId() && person.getUserName() == this.getUserName()) {
return true;
}
}
}
return false;}`
hashCode的作用
hashCode() 的作用是获取哈希码,也称为散列码;它实际上是返回一个int整数。这个哈希码的作用是确定该对象在哈希表中的索引位置。
hashCode() 定义在JDK的Object.java中,这就意味着Java中的任何类都包含有hashCode() 函数。
仅仅当创建并某个“类的散列表”(关于“散列表”见下面说明)时,该类的hashCode() 才有用
(作用是:确定该类的每一个对象在散列表中的位置;其它情况下(例如,创建类的单个对象,或者创建类的对象数组等等),类的hashCode() 没有作用。
-
hashCode() 在散列表中才有用,在其它情况下没用。在散列表中hashCode() 的作用是获取对象的散列码,进而确定该对象在散列表中的位置
-
1、如果两个对象相等,那么它们的hashCode()值一定相同。 这里的相等是指,通过equals()比较两个对象时返回true
-
2.、如果两个对象hashCode()相等,它们并不一定相等。 因为在散列表中,hashCode()相等,即两个键值对的哈希值相等。然而哈希值相等,并 不一定能得出键值对相等。补充说一句:“两个不同的键值对,哈希值相等”,这就是哈希冲突。
-
3.如果单单只是覆盖equals()方法。我们会发现,equals()方法没有达到我们想要的效果。试试啥效果呗
Person p1 = new Person(1, "james"); Person p2 = new Person(1, "james"); HashSet<Person> set = new HashSet<>(); set.add(p1); set.add(p2); System.out.println(p1.hashCode() + "=====" + p2.hashCode());
-
若不重写,同一个对象会在hash中占据两个位置哈希位置,然而对象内容一样,完全不合理。我们在使用hash取值时,会得得到两个一模一样的结果
-
equals相等,hashCode必定相等
我们在hashMap,hashSet等哈希表中出入数据的时候
首先通过hash算法算出当前数据的hashCode,如果算出当前hashCode位置上有值,我们需要使用
equals方法比较内容是否一致,如果一致,无需保存,如果不一样,就覆盖
因此我们可以导论出一个结果,如果两个对象equals相等,hashCode必定相等 -
这也就解释了为什么equals()相等,则hashCode()必须相等。如果两个对象equals()相等,则它们在哈希表(如HashSet、HashMap等)中只应该出现一次;如果hashCode()不相等,那么它们会被散列到哈希表的不同位置,哈希表中出现了不止一次。
了解一波String 类中的equals 和 hashCode方法
先看代码
`private final char value[];
private int hash; // Default to 0
public boolean equals(Object anObject) {
if (this == anObject) {
return true;
}
if (anObject instanceof String) {
String anotherString = (String)anObject;
int n = value.length;
if (n == anotherString.value.length) {
char v1[] = value;
char v2[] = anotherString.value;
int i = 0;
while (n-- != 0) {
if (v1[i] != v2[i])
return false;
i++;
}
return true;
}
}
return false;
}
public int hashCode() {
int h = hash;
if (h == 0 && value.length > 0) {
char val[] = value;
for (int i = 0; i < value.length; i++) {
h = 31 * h + val[i];
}
hash = h;
}
return h;
} `
-
String的数据是final的,即一个String对象一旦创建,便不能修改;形如String s = “hello”; s = “world”;的语句,当s = “world”执行时,并不是字符串对象的值变为了”world”,而是新建了一个String对象,s引用指向了新对象
-
String类将hashCode()的结果缓存为hash值,提高性能
-
String对象equals()相等的条件是二者同为String对象,长度相同,且字符串值完全相同;不要求二者是同一个对象。
-
String的hashCode()计算公式为:s[0]*31^(n-1) + s[1]*31^(n-2) + … + s[n-1]
关于hashCode()计算过程中,为什么使用了数字31,主要有以下原因: -
使用质数计算哈希码,由于质数的特性,它与其他数字相乘之后,计算结果唯一的概率更大,哈希冲突的概率更小。
-
使用的质数越大,哈希冲突的概率越小,但是计算的速度也越慢;31是哈希冲突和性能的折中,实际上是实验观测的结果。
-
JVM会自动对31进行优化:31 * i == (i << 5) – i
-
这里我们看到31 = 1111…于是乎我们在做获取hashcode时,31是哈希冲突和性能的折中,实际上是实验观测的结果
java三种移位运算符
<< : 左移运算符,num << 1,相当于num乘以2
>> : 右移运算符,num >> 1,相当于num除以2
>>> : 无符号右移,忽略符号位,空位都以0补齐
我们如何写hashCode,我想既然String的源码中是这样写的,我们不如模仿一波
-
重写hashcode()的原则 所谓原则也就是怎样能达到不能触及它的最低和最高的效率问题
通过前面的描述我们知道,重写hashCode需要遵守以下原则: -
(1)如果重写了equals()方法,检查条件“两个对象使用equals()方法判断为相等,则hashCode()方法也应该相等”是否成立,如果不成立,则重写hashCode ()方法。 注:我是这样理解,如果equals相等,说明两个对象存在于一个hashCode地址的位置中 hashCode()的方法自然是相等的
-
(2)hashCode()方法不能太过简单,否则哈希冲突过多。
-
(3)hashCode()方法不能太过复杂,否则计算复杂度过高,影响性能。
《Effective Java》中提出了一种简单通用的hashCode算法
-
A、初始化一个整形变量,为此变量赋予一个非零的常数值,比如int result = 17;
-
B、选取equals方法中用于比较的所有域(之所以只选择equals()中使用的域,是为了保证上述原则的第1条),然后针对每个域的属性进行计算:
-
(1) 如果是boolean值,则计算f ? 1:0
-
(2) 如果是byte\char\short\int,则计算(int)f
-
(3) 如果是long值,则计算(int)(f ^ (f >>> 32))
-
(4) 如果是float值,则计算Float.floatToIntBits(f)
-
(5) 如果是double值,则计算Double.doubleToLongBits(f),然后返回的结果是long,再用规则(3)去处理long,得到int
-
(6) 如果是对象应用,如果equals方法中采取递归调用的比较方式,那么hashCode中同样采取递归调用hashCode的方式。否则需要为这个域计算一个范式,比如当这个域的值为null的时候,那么hashCode 值为0
-
(7) 如果是数组,那么需要为每个元素当做单独的域来处理。java.util.Arrays.hashCode方法包含了8种基本类型数组和引用数组的hashCode计算,算法同上。
-
C、最后,把每个域的散列码合并到对象的哈希码中。
下面通过一个例子进行说明。在该例中,Person类重写了equals()方法和hashCode()方法。因为equals()方法中只使用了name域和age域,所以hashCode()方法中,也只计算name域和age域。
简便hashCode方法
public int hashCode() {
int hash = 17;
hash = hash * 31 + getName().hashCode();
hash = hash * 31 + getAge();
return hash;
}