Java容器(六):从容器出发探讨hashCode和equals

£神魔★判官ぃ 2022-05-16 03:34 183阅读 0赞

一、容器中的hashcode和euqals

  Java容器框架中有List和Set,其中List允许有重复元素,而Set则不允许有重复元素,Set是如何处理这里重复元素的?肯定是与equals相关,通过迭代来equals()是否相等,但是当数据量大的时候,假如我们往HashSet中添加10000个元素,equals()10000次,效率岂不是很低?我们来看看HashSet是如何实现的

  1. public V put(K key, V value) {
  2. //如果key为空的情况
  3. if (key == null)
  4. return putForNullKey(value);
  5. //计算key的hash值
  6. int hash = hash(key);
  7. //计算该hash值在table中的下标
  8. int i = indexFor(hash, table.length);
  9. //对table[i]存放的链表进行遍历
  10. for (Entry<K,V> e = table[i]; e != null; e = e.next) {
  11. Object k;
  12. //判断该条链上是否有hash值相同的(key相同)
  13. //若存在相同,则直接覆盖value,返回旧value
  14. if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
  15. V oldValue = e.value;
  16. e.value = value;
  17. e.recordAccess(this);
  18. return oldValue;
  19. }
  20. }
  21. //修改次数+1
  22. modCount++;
  23. //把当前key,value添加到table[i]的链表中
  24. addEntry(hash, key, value, i);
  25. return null;
  26. }

HashSet内部是使用HashMap来实现的

当调用set.add(1),实际上set在内部把添加的值1当做key,把空的object对象当做value,使用内部的map添加该key-value

  当我们往HashMap中添加一个key-value时,首先会为key计算一个hash值,然后通过该hash值求得该key应该在哈希表的哪个索引位置,然后对该位置的链表进行遍历,如果不存在与该key对应的hash值,则存入;如果存在和key相同的hash值,就调用equals方法来匹配这两个元素是否相同。

  从上面可以看到,Set其实是通过hashcode来减少了euqals的次数,从而提升效率,也就是说hashcode和euqals是紧密联系的。

二、hashCode和equals

  在Effective Java中的第8条和第9条中分别提到了对equals和hashCode的规则
  
  对于euqals应该遵守如下约定:
  1、自反性:x.equals(x) 必须为true
  2、对称性:如果x.equals(y),则y.euqals(x)必须为true
  3、传递性:如果x.equals(y)返回是“true”,而且y.equals(z)返回是“true”,那么z.equals(x)也应该返回是“true”
  4、一致性:如果x.equals(y)返回是“true”,只要x和y内容一直不变,不管你重复x.equals(y)多少次,返回都是“true”
  5、任何情况下,x.equals(null),永远返回是“false”;x.equals(和x不同类型的对象)永远返回是“false”
  6、覆盖equals时总是要覆盖hashCode

  对于hashCode应该遵守如下约定:
  1、在一个应用程序执行期间,如果一个对象的equals方法做比较所用到的信息没有被修改的话,则对该对象调用hashCode方法多次,它必须始终如一地返回同一个整数。
  2、如果两个对象根据equals(Object o)方法是相等的,则调用这两个对象中任一对象的hashCode方法必须产生相同的整数结果。
  3、如果两个对象根据equals(Object o)方法是不相等的,则调用这两个对象中任一个对象的hashCode方法,不要求产生不同的整数结果。但如果能不同,则可能提高散列表的性能。

  总结:
  1、如果x.equals(y)返回“true”,那么x和y的hashCode()必须相等。
  2、如果x.equals(y)返回“false”,那么x和y的hashCode()有可能相等,也有可能不等。

三、举例

  1. class Student{
  2. int age;
  3. int sex;
  4. String name;
  5. public Student(int age, int sex, String name) {
  6. super();
  7. this.age = age;
  8. this.sex = sex;
  9. this.name = name;
  10. }
  11. //省略get和set方法
  12. @Override
  13. public int hashCode() {
  14. System.out.println("调用了hashCode方法...");
  15. int hashResult = 1;
  16. hashResult = (hashResult + Integer.valueOf(age).hashCode() + name.hashCode()) * 99;
  17. System.out.println("name:"+name +" hashCode:" + hashResult);
  18. return hashResult;
  19. }
  20. @Override
  21. public boolean equals(Object obj) {
  22. System.out.println("调用了equals方法...");
  23. if(obj == null)
  24. return false;
  25. if(obj == this)
  26. return true;
  27. if(obj.getClass() != this.getClass())
  28. return false;
  29. Student p = (Student) obj;
  30. if(getAge() != p.getAge() || getSex() != p.getSex())
  31. return false;
  32. if(getName() != null){
  33. if(!getName().equals(p.getName()))
  34. return false;
  35. }
  36. return true;
  37. }
  38. }
  39. public class Equals1 {
  40. public static void main(String[] args){
  41. Student p1 = new Student(1, 1, "张飞");
  42. Student p2 = new Student(2, 1, "关羽");
  43. Student p3 = new Student(1, 1, "张飞");
  44. Student p4 = new Student(1, 1, "关羽");
  45. System.out.println("p1==p3 : " + (p1 == p3));
  46. System.out.println("p1.equals(p3) : " + (p1.equals(p3)));
  47. System.out.println();
  48. HashSet<Student> set = new HashSet<Student>();
  49. set.add(p1);
  50. set.add(p2);
  51. set.add(p3);
  52. set.add(p4);
  53. System.out.println("set.size : " + set.size());
  54. }
  55. }

结果如下:

  1. p1==p3 : false
  2. 调用了equals方法...
  3. p1.equals(p3) : true
  4. 调用了hashCode方法...
  5. name:张飞 hashCode:78610752
  6. 调用了hashCode方法...
  7. name:关羽 hashCode:67229415
  8. 调用了hashCode方法...
  9. name:张飞 hashCode:78610752
  10. 调用了equals方法...
  11. 调用了hashCode方法...
  12. name:关羽 hashCode:67229316
  13. set.size : 3

  分析:
  1、p1 和 p3 的属性相同,但是他们指向不同的对象,所以p1==p3为false
  2、p1 和 p3 虽然指向不同的对象,但属性相同,因此equals返回true
  3、Student类覆盖了hashCode和equals方法,且hashcode值通过类的age和name属性来求得,p1 和 p3 具有相同的属性,当增加p3时,由于hashcode相同,因此会调用equals,最后发现值相同,所以去除重复

发表评论

表情:
评论列表 (有 0 条评论,183人围观)

还没有评论,来说两句吧...

相关阅读

    相关 equalshashCode

    equals是比较两个对象是否一样。若类没有重写equals方法,则会使用Object类中的equals方法,Object中equals方法是==比较,是比较栈内存中的引用地址

    相关 equalshashcode总结

    equals和hashcode总结: 1.equals方法没有重写的话,用于判断对象的内存地址引用是否是用一个地址。重写之后一般用来比较对象的内容是否相等(比如student