Java基础深度总结:Object类-hashCode方法
行动是治愈恐惧的良药,而犹豫、拖延将不断滋养恐惧。
1.Hash概述
Hash称为“散列”或者音译“哈希”,本质上是一种压缩映射,也就是说把任意长度的消息,通过散列算法压缩成某一固定长度的消息,由于散列的空间通常远小于输入的空间,因此不同的输入可能会散列出相同的输出。
2.hashCode的作用
hashCode方法返回一个hash code值,这个方法是为了更好的支持hash表,例如:HashMap、HashTable等,
在使用hash表这类数据结构时,首先需要通过hash函数计算出一个hash值,然后根据这hash值,放入到对应的Hash表对应的Key上,如果不同的对象确产生了相同的hash值,也就是发生了Hash值相同导致冲突的情况,那么就在这个Hash key的地方产生一个链表,将所有产生相同hash值的对象放到这个单链表上去,串在一起。
查找某个对象时,首先会计算出要查询对象的hash值,通过hash值确定该对象在哪个链表中,然后用equals方法依次判断该对象和链表上的对象是否相等。
- 提高了对象的查询效率,只有在发生了hash冲突的情况下,才需要通过线性查找对象。
- 如果两个对象相同,那么它们的hash值一定相同;如果两个对象的hash值相同,并不一定表示两个对象相同,但他们一定在同一个链表上。
Tips:
- hashCode方法返回一个32位的int整数,hash值的取值范围是: -2^31 ~ 2^31 -1 ,因此若存在 2^32 + 1个对象,那么一定会有两个不同的对象有一样的hash值。
- Object.hashCode返回的hash值不是对象内存地址。
3.为什么重写equals的同时还得重写hashCode(重点)
因为如果你只重写了equals方法而没有重写hashCode方法,那么在使用散列这样的数据结构时,没办法处理好你的键。这事实上违反了Object hashCode通约的第二条。
java.lnag.Object中对hashCode通约:
- 在一个应用程序执行期间,如果一个对象的equals方法做比较所用到的信息没有被修改的话,则对该对象调用hashCode方法多次,它必须始终如一地返回同一个整数。
- 如果两个对象根据equals(Object o)方法是相等的,则调用这两个对象中任一对象的hashCode方法必须产生相同的整数结果。
- 如果两个对象根据equals(Object o)方法是不相等的,则调用这两个对象中任一个对象的hashCode方法,不要求产生不同的整数结果。但如果能不同,则可能提高散列表的性能。
下面举出了一个只重写了equals方法的例子:
import java.util.HashMap;
import java.util.Map;
public class EqualsAndHashCodeTest {
public static void main(String[] args) {
Map<Key, Integer> map = new HashMap<Key, Integer>();
Key k1 = new Key("A");
Key k2 = new Key("A");
map.put(k1, 2);
System.out.println("map.get(k1):" + map.get(k1));
System.out.println("map.get(k2):" + map.get(k2));
System.out.println("k1.equals(k2):" + k1.equals(k2));
System.out.println("k1.hashCode:" + k1.hashCode());
System.out.println("k2.hashCode:" + k2.hashCode());
}
}
class Key {
private String k;
public Key(String key) {
this.k = key;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Key key = (Key) o;
return k != null ? k.equals(key.k) : key.k == null;
}
}
输出:
map.get(k1):2
map.get(k2):null
k1.equals(k2):true
k1.hashCode:21685669
k2.hashCode:2133927002
解析:
可以看到,k1和k2是等价的,但map.get(k2)并没有拿到我们想要取得的对象,因为Key并没有去重写hashCode方法,所以k1、k2的hash值是不一样的,所以以k2的hash值是无法正确找到k1 hash值所对应值的。这也说明了,如果你只实现equals方法而没有实现hashCode方法,那么将会给你在使用散列这类数据结构时埋下隐患!
4.hashCode的实现
1.把某个非0的常数值,比如17,保存在一个名为result的int类型的变量中。
2.对于对象中的每个域,做如下操作:
a.为该域计算int类型的哈希值c:
- 如果该域是boolean类型,则计算(f?1:0)
- 如果该域是byte、char、short或者int类型,则计算(int)f
- 如果该域是long类型,则计算(int)(f^(f>>>32))
- 如果该域是float类型,则计算Float.floatToIntBits(f)
- 如果该域是double类型,则计算Double.doubleToLongBits(f),然后重复第三个步骤。
- 如果该域是一个对象引用,并且该类的equals方法通过递归调用equals方法来比较这个域,同样为这个域递归的调用hashCode,如果这个域为null,则返回0。
- 如果该域是数组,则要把每一个元素当作单独的域来处理,递归的运用上述规则,如果数组域中的每个元素都很重要,那么可以使用Arrays.hashCode方法。
b.把上面计算得到的hash值c 按照result = 31*result + c 公式 加入到result中
3.返回result
示例代码:
import java.util.Arrays;
public class HashCodeExample {
private boolean flag;
private int num;
private long longNum;
private double doubleNum;
private int[] ids;
private Student student;
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
HashCodeExample that = (HashCodeExample) o;
if (flag != that.flag) return false;
if (num != that.num) return false;
if (longNum != that.longNum) return false;
if (Double.compare(that.doubleNum, doubleNum) != 0) return false;
if (!Arrays.equals(ids, that.ids)) return false;
return student != null ? student.equals(that.student) : that.student == null;
}
@Override
public int hashCode() {
int result;
long temp;
//boolean
result = (flag ? 1 : 0);
//int
result = 31 * result + num;
//long
result = 31 * result + (int) (longNum ^ (longNum >>> 32));
//double
temp = Double.doubleToLongBits(doubleNum);
result = 31 * result + (int) (temp ^ (temp >>> 32));
//数组
result = 31 * result + Arrays.hashCode(ids);
//引用
result = 31 * result + (student != null ? student.hashCode() : 0);
return result;
}
}
class Student{
private int id;
private String name;
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Student student = (Student) o;
if (id != student.id) return false;
return name != null ? name.equals(student.name) : student.name == null;
}
@Override
public int hashCode() {
int result = id;
result = 31 * result + (name != null ? name.hashCode() : 0);
return result;
}
}
Tips:
- 计算hash值时必须排除equals比较时没有用到的域,否则可能会违反Object.hashCode通约的第二条。
- 31是个奇素数,而且31的乘法可以用移位和减法替代:31* i == ( i << 5 ) - i ,JVM会自动优化的。
- hashCode和equals方法都可以用开发工具的快捷键生成的。