@linux1s1s
2017-07-24T17:33:38.000000Z
字数 4204
阅读 1336
Java
2017-07
为了搞懂Java中
equals()
和hashCode()
方法,我们先从一个具体的问题入手。
直接看下面的实例Code
class Student {
private int number;
private String name;
public Student(int number, String name) {
this.number = number;
this.name = name;
}
public int getNumber() {
return number;
}
public void setNumber(int number) {
this.number = number;
}
@Override
public String toString() {
return "Student{" +
"number=" + number +
", name='" + name + '\'' +
'}';
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
上面是典型的POJO类,接着我们实例化这个类,并且将实例化后的实例添加到Set容器中。
private void profile() {
HashSet<Student> hs = new HashSet<>();
Student s1, s2, s3;
hs.add(s1 = new Student(1, "zhangsan"));
hs.add(s2 = new Student(2, "lisi"));
hs.add(s3 = new Student(1, "zhangsan"));
Iterator<Student> it = hs.iterator();
while (it.hasNext()) {
Student s = it.next();
Log.i("Student", s.toString());
}
}
上面故意将实例化的Student有两处内容相同,接下来观察一下打印出来的结果
07-24 16:47:22.420 29850-29850/ I/Student: Student{number=2, name='lisi'}
07-24 16:47:22.420 29850-29850/ I/Student: Student{number=1, name='zhangsan'}
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
public boolean add(E e) {
return map.put(e, PRESENT)==null;
}
java.util.HashMap.java
/**
* Adds a new entry with the specified key, value and hash code to
* the specified bucket. It is the responsibility of this
* method to resize the table if appropriate.
*
* Subclass overrides this to alter the behavior of put method.
*/
public V put(K key, V value) {
if (table == EMPTY_TABLE) {
inflateTable(threshold);
}
if (key == null)
return putForNullKey(value);
int hash = sun.misc.Hashing.singleWordWangJenkinsHash(key);
int i = indexFor(hash, table.length);
for (HashMapEntry<K,V> e = table[i]; e != null; e = e.next) {
Object k;
//1.Hash code 相同
//2. key地址相同或者key满足equals条件
//仅当以上条件都满足的情况下,不会将key,value添加到Entry容器
if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
V oldValue = e.value;
e.value = value;
e.recordAccess(this);
return oldValue;
}
}
modCount++;
addEntry(hash, key, value, i);
return null;
}
定位到L22,我们看到在判断语句中分别调用了hash和equals()方法,如果hash code不同则直接加入Entry容器中,或者加入容器的实例不满足equals条件也会直接加入Entry容器。所以根据L19开始的注释得知,Student内容相同的两个实例必然满足如下条件:
Hash code
不相同Student
地址不相同且Student
不满足equals
条件我们来验证一下刚才的推测:
private void profile() {
HashSet<Student> hs = new HashSet<>();
Student s1, s2, s3;
hs.add(s1 = new Student(1, "zhangsan"));
hs.add(s2 = new Student(2, "lisi"));
hs.add(s3 = new Student(1, "zhangsan"));
Log.i("Student", "s1 hashcode: " + s1.hashCode());
Log.i("Student", "s2 hashcode: " + s2.hashCode());
Log.i("Student", "s3 hashcode: " + s3.hashCode());
Log.i("Student", "s1.equals(s2): " + s1.equals(s2));
Log.i("Student", "s2.equals(s3): " + s2.equals(s3));
Log.i("Student", "s3.equals(s1): " + s3.equals(s1));
Iterator<Student> it = hs.iterator();
while (it.hasNext()) {
Student s = it.next();
Log.i("Student", s.toString());
}
}
验证结果:
07-24 17:23:37.372 1454-1454/ I/Student: s1 hashcode: 36165334
07-24 17:23:37.372 1454-1454/ I/Student: s2 hashcode: 233823575
07-24 17:23:37.372 1454-1454/ I/Student: s3 hashcode: 215740484
07-24 17:23:37.372 1454-1454/ I/Student: s1.equals(s2): false
07-24 17:23:37.372 1454-1454/ I/Student: s2.equals(s3): false
07-24 17:23:37.372 1454-1454/ I/Student: s3.equals(s1): false
07-24 17:23:37.372 1454-1454/ I/Student: Student{number=2, name='lisi'}
07-24 17:23:37.372 1454-1454/ I/Student: Student{number=1, name='zhangsan'}
07-24 17:23:37.372 1454-1454/ I/Student: Student{number=1, name='zhangsan'}
经过上面的验证,猜测正确,那么如果想完成实际开发中的要求(相同内容的实例不能同时加入到Set容器中),必须重写Student这个类的equals()
和hashCode()
方法。
重写Student类中的equals()
和hashCode()
方法
@Override
public boolean equals(Object obj) {
Student s = (Student) obj;
return this.number == s.number && this.name.equals(s.name);
}
@Override
public int hashCode() {
return this.number * this.name.hashCode();
}
验证结果
07-24 17:29:26.653 7938-7938/ I/Student: s1 hashcode: -1432604556
07-24 17:29:26.653 7938-7938/ I/Student: s2 hashcode: 6644006
07-24 17:29:26.653 7938-7938/ I/Student: s3 hashcode: -1432604556
07-24 17:29:26.653 7938-7938/ I/Student: s1.equals(s2): false
07-24 17:29:26.653 7938-7938/ I/Student: s2.equals(s3): false
07-24 17:29:26.653 7938-7938/ I/Student: s3.equals(s1): true
07-24 17:29:26.653 7938-7938/ I/Student: Student{number=2, name='lisi'}
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()方法详解