[关闭]
@linux1s1s 2017-07-24T09:33:38.000000Z 字数 4204 阅读 705

Java equals hashCode 浅析

Java 2017-07


为了搞懂Java中equals()hashCode()方法,我们先从一个具体的问题入手。

问题

直接看下面的实例Code

  1. class Student {
  2. private int number;
  3. private String name;
  4. public Student(int number, String name) {
  5. this.number = number;
  6. this.name = name;
  7. }
  8. public int getNumber() {
  9. return number;
  10. }
  11. public void setNumber(int number) {
  12. this.number = number;
  13. }
  14. @Override
  15. public String toString() {
  16. return "Student{" +
  17. "number=" + number +
  18. ", name='" + name + '\'' +
  19. '}';
  20. }
  21. public String getName() {
  22. return name;
  23. }
  24. public void setName(String name) {
  25. this.name = name;
  26. }
  27. }

上面是典型的POJO类,接着我们实例化这个类,并且将实例化后的实例添加到Set容器中。

  1. private void profile() {
  2. HashSet<Student> hs = new HashSet<>();
  3. Student s1, s2, s3;
  4. hs.add(s1 = new Student(1, "zhangsan"));
  5. hs.add(s2 = new Student(2, "lisi"));
  6. hs.add(s3 = new Student(1, "zhangsan"));
  7. Iterator<Student> it = hs.iterator();
  8. while (it.hasNext()) {
  9. Student s = it.next();
  10. Log.i("Student", s.toString());
  11. }
  12. }

上面故意将实例化的Student有两处内容相同,接下来观察一下打印出来的结果

  1. 07-24 16:47:22.420 29850-29850/ I/Student: Student{number=2, name='lisi'}
  2. 07-24 16:47:22.420 29850-29850/ I/Student: Student{number=1, name='zhangsan'}
  3. 07-24 16:47:22.420 29850-29850/ I/Student: Student{number=1, name='zhangsan'}

可以看到Set容器中出现了我们认为重复的实例(内容重复),而这在实际工作中,需要排除这种内容完全重复的实例,想一想在哪些地方要注意处理(其实在C端开发中,比如Tab页面,Server给出数据,如果不小心给出完全相同的数据,那么C端就要避免出现几个完全相同的页面同时出现)。那么是什么原因导致出现上面的情况呢?

探究原因

我们先来看Set的add源码

java.util.HashSet.java

  1. public boolean add(E e) {
  2. return map.put(e, PRESENT)==null;
  3. }

java.util.HashMap.java

  1. /**
  2. * Adds a new entry with the specified key, value and hash code to
  3. * the specified bucket. It is the responsibility of this
  4. * method to resize the table if appropriate.
  5. *
  6. * Subclass overrides this to alter the behavior of put method.
  7. */
  8. public V put(K key, V value) {
  9. if (table == EMPTY_TABLE) {
  10. inflateTable(threshold);
  11. }
  12. if (key == null)
  13. return putForNullKey(value);
  14. int hash = sun.misc.Hashing.singleWordWangJenkinsHash(key);
  15. int i = indexFor(hash, table.length);
  16. for (HashMapEntry<K,V> e = table[i]; e != null; e = e.next) {
  17. Object k;
  18. //1.Hash code 相同
  19. //2. key地址相同或者key满足equals条件
  20. //仅当以上条件都满足的情况下,不会将key,value添加到Entry容器
  21. if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
  22. V oldValue = e.value;
  23. e.value = value;
  24. e.recordAccess(this);
  25. return oldValue;
  26. }
  27. }
  28. modCount++;
  29. addEntry(hash, key, value, i);
  30. return null;
  31. }

定位到L22,我们看到在判断语句中分别调用了hash和equals()方法,如果hash code不同则直接加入Entry容器中,或者加入容器的实例不满足equals条件也会直接加入Entry容器。所以根据L19开始的注释得知,Student内容相同的两个实例必然满足如下条件:

我们来验证一下刚才的推测:

  1. private void profile() {
  2. HashSet<Student> hs = new HashSet<>();
  3. Student s1, s2, s3;
  4. hs.add(s1 = new Student(1, "zhangsan"));
  5. hs.add(s2 = new Student(2, "lisi"));
  6. hs.add(s3 = new Student(1, "zhangsan"));
  7. Log.i("Student", "s1 hashcode: " + s1.hashCode());
  8. Log.i("Student", "s2 hashcode: " + s2.hashCode());
  9. Log.i("Student", "s3 hashcode: " + s3.hashCode());
  10. Log.i("Student", "s1.equals(s2): " + s1.equals(s2));
  11. Log.i("Student", "s2.equals(s3): " + s2.equals(s3));
  12. Log.i("Student", "s3.equals(s1): " + s3.equals(s1));
  13. Iterator<Student> it = hs.iterator();
  14. while (it.hasNext()) {
  15. Student s = it.next();
  16. Log.i("Student", s.toString());
  17. }
  18. }

验证结果:

  1. 07-24 17:23:37.372 1454-1454/ I/Student: s1 hashcode: 36165334
  2. 07-24 17:23:37.372 1454-1454/ I/Student: s2 hashcode: 233823575
  3. 07-24 17:23:37.372 1454-1454/ I/Student: s3 hashcode: 215740484
  4. 07-24 17:23:37.372 1454-1454/ I/Student: s1.equals(s2): false
  5. 07-24 17:23:37.372 1454-1454/ I/Student: s2.equals(s3): false
  6. 07-24 17:23:37.372 1454-1454/ I/Student: s3.equals(s1): false
  7. 07-24 17:23:37.372 1454-1454/ I/Student: Student{number=2, name='lisi'}
  8. 07-24 17:23:37.372 1454-1454/ I/Student: Student{number=1, name='zhangsan'}
  9. 07-24 17:23:37.372 1454-1454/ I/Student: Student{number=1, name='zhangsan'}

经过上面的验证,猜测正确,那么如果想完成实际开发中的要求(相同内容的实例不能同时加入到Set容器中),必须重写Student这个类的equals()hashCode()方法。

解决

重写Student类中的equals()hashCode()方法

  1. @Override
  2. public boolean equals(Object obj) {
  3. Student s = (Student) obj;
  4. return this.number == s.number && this.name.equals(s.name);
  5. }
  6. @Override
  7. public int hashCode() {
  8. return this.number * this.name.hashCode();
  9. }

验证结果

  1. 07-24 17:29:26.653 7938-7938/ I/Student: s1 hashcode: -1432604556
  2. 07-24 17:29:26.653 7938-7938/ I/Student: s2 hashcode: 6644006
  3. 07-24 17:29:26.653 7938-7938/ I/Student: s3 hashcode: -1432604556
  4. 07-24 17:29:26.653 7938-7938/ I/Student: s1.equals(s2): false
  5. 07-24 17:29:26.653 7938-7938/ I/Student: s2.equals(s3): false
  6. 07-24 17:29:26.653 7938-7938/ I/Student: s3.equals(s1): true
  7. 07-24 17:29:26.653 7938-7938/ I/Student: Student{number=2, name='lisi'}
  8. 07-24 17:29:26.653 7938-7938/ I/Student: Student{number=1, name='zhangsan'}

看到这里的s1和s3 hashcode相等,而且s1和s3满足equals条件,这个时候Set容器就自动屏蔽了内容相同的实例。

TIPS: 参看文章:Java提高篇——equals()与hashCode()方法详解

添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注