hashcode与equals(重要)
在了解这两个之前首先先了一下hash函数。hash函数能把任意长的输入通过散列算法变成固定长度的输出,该输出就是散列值(hash值)。
hash值的作用是什么呢?hash值是通过hash函数计算而来,能将任意长度的输入转换成固定长度的输出。并且两个不同的输入得到相同的输出是几乎不可能的,由此看来,hash值能够给一个对象分配一个唯一的身份。
根据hash值的作用我们可以分析出它的特性:独一无二、与内容有关。
常见的求hash值的hash算法有哪些呢?加法、位运算、除法等。
Java中的hashcode(), (返回值为 hashcode ). Object类中默认包含一个hashcode()方法,在没有对其进行重写的情况下,默认返回对象的地址(int),Integer与String类对其进行了重写,Integer类的hashcode返回其本身的值,String返回的hashcode与其内容有关而不是地址。
java 中引入hashcode的作用是什么呢?
简言之就是为了提高查找的效率。比如我在一个长度为10000的数组中插入数据,插入第n个数据之前都会挨个检查是否与前 n-1 个数据重复。会造成不小的资源浪费,假如我在插入时按照插入数据内容的不同为其分配一个稳定的数组下标(同一个数据多次分配的地址不会发生变化)。那么我在插入之前只需要判断此地址下是否已经存在数据即可。但是存在的问题是,如果两个不同数据分配了同一个数组下标,就会出现碰撞,解决方法也很简单,我给每个变量分配一个唯一的数组下标呗,这样就不会发生冲突了。现在问题就转化为了怎么给变量分配一个唯一数组下标的问题。根据我们上面提到的 hashcode 的计算方式,我们很容易就能想到使用变量的地址值当作它唯一的数组下标,因为不同变量内存地址是不可能相同的。到目前为止似乎已经解决了所有的问题,但是仔细想想我们好像没有考虑过数组的感受,我们可以尝试输出随便一个对象的 hashcode ,会发现这是一个超级大的值,大概几十亿,我们的电脑应该无法为我们开辟这样一个数组。所以我们接下来要做的是将 hashcode 映射到固定长度的数组上。数据结构已经学过了,使用hashcode对数组长度取余可以解决这个问题,但是一般我们不直接使用hashcode本身的值,而是对其进行一些加工(很多的位运算)得到一个叫hash的值,再用这个hash和数组长度求余,结果便是改变量存放的位置。这样可以极大程度的将不同的变量均匀连列到不同的位置,但是由于数组长度有限,不可避免的会出现碰撞的情况。解决冲突的方法有拉链法、线性探测、二次探测等。
Java 的HashMap中存放的是键值对(key-value),利用上一段的方法,使用 key 的 hashcode 得到一个数组下标,然后将 value 存入到相应的位置。从而达到在key 和 value之间建立映射的目的。
前面说过我们无法避免冲突,HashMap使用拉链法解决冲突,也就是同一个数组下标储存的是一个链表,链表中每个节点包含key,value,hashcode值。当我们通过key查找value时,根据key算出value所在的数组下标。若当前下标下有多个节点,那么再根据key值找到对应的value 返回。
那么equals和hashcode之间有什么关系呢?
import java.util.HashMap;
2 class Key {
3 private Integer id;
8 //故意先注释掉equals和hashCode方法
9 // public boolean equals(Object o) {
10 // if (o == null || !(o instanceof Key))
11 // { return false; }
12 // else
13 // { return this.getId().equals(((Key) o).getId());}
14 // }
15
16 // public int hashCode()
17 // { return id.hashCode(); }
18 }
19
20 public class WithoutHashCode {
21 public static void main(String[] args) {
22 Key k1 = new Key(1);
23 Key k2 = new Key(1);
24 HashMap<Key,String> hm = new HashMap<Key,String>();
25 hm.put(k1, "Hello");
26 System.out.println(hm.get(k2));
27 }
28 }
结果会是null。
按照我们常规的理解,k1与k2是两个一样的对象。内部的值都是1,所以在操作HashMap时k1与k2的作用应该是一样的。都能把value 取出来。
但是由于k1和k2是两个内容相同但是地址不同的对象,所以两者的 hashcode 也不相同,导致最终两个的散列地址不一样。k2也就无法取出k1的内容。
我们想要的结果是让内容相同的对象的hashcode值相等,这样就不会出现上述的问题(两个内容一模一样的key却不能取出同一个value).把注释去掉之后,让这个类的hashcode方法返回Integer对象的hashcode,前面已经说过Integer类已经重写了hashcode方法,返回对象本身,即 1 的 hashcode是 1 .这样 k1 与 k2 的 hashcode 就相同了,再次运行的结果会出乎意料,仍然是null. 因为在数组的同一个下标下,可能会有多个节点,在散列地址相同的情况下,HashMap会通过key的equals方法进行匹配,因为我们注释了重写的equals方法,k1与k2虽然内容相等,但是在equals方法中还是返回false,导致返回null.因此我们需要重写equals,让内容相等的两个对象返回true,这样就能正确使用k2取出k1的内容了。
总结下来:
1、如果两个对象相等,那么它们的hashcode也要相等
2、两个对象相等那么调用equals方法返回true
3、两个对象拥有一样的hashcode值,它们也不一定是相等的(针对已经重写的hashcode()方法而言,默认的hashcode方法返回值不可能相同)
4、equals方法与hashcode方法需要被一起重写