Java Hash集合的equals()与hashCode() 方法
Java 集合实现类,无论是HashSet、HashMap等所有的Hash算法实现的集合类(后面简称Hash集合),加入的对象必须实现 hashCode() 与 equals() 方法,稍微不同的地方是:HashSet 需要对整个对象实现两个方法,而HashMap 只需要对作为key的对象实现这两个方法。因为向Hash集合存入一个元素时,Hash集合会调用该对象的hashCode()方法来得到该对象的hashCode的值,然后根据hashCode的值决定该对象在Hash集合中存储位置。如果两个元素通过equals()方法比较返回true,但它们的hashCode()方法返回值不相等,Hash集合将会把它们视为不同的对象,两个元素都可以添加到Hash集合里。所以Hash集合判断两个元素是否相等的标准是:两个对象通过equals()方法比较相等,并且两个元素的hashCode()方法返回值也相等。
重写hashCode()方法的规则:
① 同一个对象多次调用hashCode()方法返回值相同
② 两个对象通过equals()方法比较返回true 时,这两个对象的hashCode()方法返回相等的值
③ 对象中用于equals() 方法比较标准的变量,都应该用于计算hashCode值
重写hashCode方法一般步骤
① 把对象内每个有意义的变量计算出一个int 型的hashCode值
② 用第1步计算出来的多个hashCode值组合成一个hashCode值返回
③ 为了避免直接相加产生偶然相等,可以通过为各变量的hashCode值乘以任意质数再相加
案例:
① 首先来看一个没有重写equals() 与 hashCode() 方法的对象,添加到HashSet 时会出现什么情况。
我们创建一个Person类,包含两个成员变量,id 与 name,如果两个对象的这两个属性都不等我们才认为它们是不同的对象,我们没有重写equals() 与 hashCode() 方法,但是我们希望HashSet 集合对同一个对象只能保存一份,有相同对象加入时,加入失败。
1 class Person 2 { 3 private String name; 4 private int id; 5 6 public Person() 7 {} 8 9 public Person(int id,String name) 10 { 11 this.id = id; 12 this.name = name; 13 } 14 15 public int getId() { 16 return id; 17 } 18 19 public void setId(int id) { 20 this.id = id; 21 } 22 23 public String getName() { 24 return name; 25 } 26 public void setName(String name) { 27 this.name = name; 28 } 31 }
1 2 HashSet haSe = new HashSet(); 3 4 //相同id与name的添加两次 5 haSe.add(new Person(1001, "latiny")); 6 haSe.add(new Person(1001, "latiny")); 7 8 //遍历HashSet 9 for(Object obj:haSe) 10 { 11 Person p = (Person)obj; 12 System.out.println(p.getId()+" "+p.getName()+" "+p.hashCode()); 13 } 14
输出结果:
1001 latiny 355165777
1001 latiny 1807500377
可以看到name与id相同的两个对象都成功的加入到了HashSet集合,因为它们的hashCode值不一样,这显然没有达到预期的效果,如果要实现预期的效果需要重写Person类 equals() 与 hashCode() 方法。
② 重写Person类equals() 与 hashCode() 方法
在上面的Person类添加如下代码:
1 //重写hashCode() 方法 2 public int hashCode() { 3 int hash = 7; 4 hash = hash*31 + this.id*11 + this.name.hashCode()*31; 5 return hash; 6 } 7 8 //重写equals() 方法 9 public boolean equals(Object obj) { 10 if(this==obj) 11 { 12 return true; 13 } 14 //如果两个对象的id与name都相等,则两个对象相等 15 if(obj.getClass()==Person.class) 16 { 17 Person p=(Person)obj; 18 return this.id==p.id && this.name==p.name; 19 } 20 else 21 { 22 return false; 23 } 24 }
重新执行代码:
1 2 HashSet haSe = new HashSet(); 3 4 //相同id与name的对象添加两次 5 haSe.add(new Person(1001, "latiny")); 6 System.out.println(haSe.add(new Person(1001, "latiny"))); 7 8 //遍历HashSet 9 for(Object obj:haSe) 10 { 11 Person p = (Person)obj; 12 System.out.println(p.getId()+" "+p.getName()+" "+p.hashCode()); 13 } 14
输出结果:
false
1001 latiny -46445433
可以看到 id与 name 相同的两个对象只有一个加入到了HashSet集合,第二次添加时失败,因为我们重写了equals() 与 hashCode() 方法,只要Person类对象id与name都相同,equals()返回true,hashCode() 返回值相同,HashSet集合就会将这两个对象视为同一个对象。
HashMap 的使用与HashSet类似,作为key的对象也需要重写 equals() 与 hashCode() 方法,不然也会出现将相同对象作为key值成功插入到HashMap中。
① 未重写 equals() 与 hashCode() 方法
1 class Person 2 { 3 private String name; 4 private int id; 5 6 public Person() 7 {} 8 9 public Person(int id,String name) 10 { 11 this.id = id; 12 this.name = name; 13 } 14 15 public int getId() { 16 return id; 17 } 18 19 public void setId(int id) { 20 this.id = id; 21 } 22 23 public String getName() { 24 return name; 25 } 26 public void setName(String name) { 27 this.name = name; 28 } 29 30 }
将Person类作为HashMap的key
1 2 HashMap<Person, String> hash = new HashMap<Person, String>(); 3 hash.put(new Person(1, "latiny"), "周瑜"); 4 hash.put(new Person(1, "latiny"), "曹操"); 5 hash.put(new Person(1, "latiny"), "刘禅"); 6 7 //foreach遍历HashMap 8 for(Object key:hash.keySet()) 9 { 10 String name = hash.get(key); 11 System.out.println(key+"-->"+name+" "+key.hashCode()); 12 } 13 14 Object obj = hash.get(new Person(1, "latiny")); 15 String name = (String) obj; 16 System.out.println(name);
输出结果:
com.latiny.set.Person@544a5ab2-->刘禅 1414159026
com.latiny.set.Person@152b6651-->曹操 355165777
com.latiny.set.Person@6bbc4459-->周瑜 1807500377
null
可以看到,id 与 name 相同的Person对象作为 key 重复添加到HashMap集合中。
更为严重的问题是,当我们想要以相同的 id 与 name的Person对象作为key去获取value 时,结果竟然是null,为什么呢?因为这个Person对象的id 与 name 与之前的三个对象相同,但是在内存中它却是一个新的对象,有自己独立的空间,即有自己独立的hashCode值,由于我们没有重写hashCode方法,它的hashCode计算方法还是按照Object这个类来实现的。
② 重写 equals() 与 hashCode() 方法之后
1 class Person 2 { 3 private String name; 4 private int id; 5 6 public Person() 7 {} 8 9 public Person(int id,String name) 10 { 11 this.id = id; 12 this.name = name; 13 } 14 15 public int getId() { 16 return id; 17 } 18 19 public void setId(int id) { 20 this.id = id; 21 } 22 23 public String getName() { 24 return name; 25 } 26 public void setName(String name) { 27 this.name = name; 28 } 29 30 31 //重写hashCode() 方法 32 public int hashCode() { 33 int hash = 7; 34 hash = hash*31 + this.id*11 + this.name.hashCode()*31; 35 return hash; 36 } 37 38 //重写equals() 方法 39 public boolean equals(Object obj) { 40 if(this==obj) 41 { 42 return true; 43 } 44 //如果两个对象的id与name都相等,则两个对象相等 45 if(obj.getClass()==Person.class) 46 { 47 Person p=(Person)obj; 48 return this.id==p.id && this.name==p.name; 49 } 50 else 51 { 52 return false; 53 } 54 } 55 56 }
执行测试代码
1 2 HashMap<Person, String> hash = new HashMap<Person, String>(); 3 hash.put(new Person(1, "latiny"), "周瑜"); 4 hash.put(new Person(1, "latiny"), "曹操"); 5 hash.put(new Person(1, "latiny"), "刘禅"); 6 7 //foreach遍历HashMap 8 for(Object key:hash.keySet()) 9 { 10 String name = hash.get(key); 11 System.out.println(key+"-->"+name+" "+key.hashCode()); 12 } 13 14 Object obj = hash.get(new Person(1, "latiny")); 15 String name = (String) obj; 16 System.out.println(name); 17
输出结果:
com.latiny.set.Person@fd3b218f-->刘禅 -46456433
刘禅
可以看到,最后一次添加的元素将之前添加的都覆盖了,因为我们重写的方法判断如果Person类的id与name相同,equals()返回true, hashCode() 返回相同的值,HashMap认定它们key值相同,会将之前加入的元素覆盖。我们也可以将具有相同的id与name的Person类作为key值去访问value。