在Java中通常使用equals方法和hashCode方法来直接地或间接地判断两个对象是否等价,并且在Java的一些库中有可能直接调用这些函数来判断等价性,例如在HashSet和HashMap中就通过调用hashCode方法来获得一个对象的哈希值,从而区分对象和维护数据结构.然而Java为对象设置的默认equals方法和hashCode方法都是基于对象地址的,所以我们有必要根据类的各个域和实际需求重新设计equals方法和hashCode方法.根据实现的不同,我们分可变类和不可变类讨论.
不可变类
equals方法
equals方法,和其他方法相同,继承自一个类的父类,那么当我们从头开始构建一个类的时候,equals方法就继承自Object类,如下:
public boolean equals(Object obj) {
return (this == obj);
}
可以看到返回的是this == obj的结果,==在对对象应用是比较的是对象地址,这一般不是我们想要的,我们一般需要通过对象的某些内容来区分等价与否,例如对定义如下的Employee类,我们希望通过区分员工的名字和id来区分不同的员工.
public class Employee {
final String name;
final String ID;
final String position;
final String phone;
public Employee(String name, String ID String position, String phone){
this.name = name;
this.ID = ID;
this.position = position;
this.phone = phone;
}
//Other Methods...
}
那么我们就可以如下地重写equals方法.
@Override
public boolean equals(Object o) {
if(!(o instanceof Employee))
return false;
Employee ano = (Employee)o;
return (this.name.equals(ano.name) && this.ID.equals(ano.ID));
}
值得注意的是,我们在对比name和ID之前首先判断参数是不是一个Employee对象,如果不是,那么更不用说等价的问题了.
hashCode方法
Java中对hashCode方法的一般要求是,对一个对象每次调用时返回相同的int类型数,并且当两个对象使用equals方法判断等价时时返回的值相等.默认的,也就是Object类的hashCode方法相当复杂,它根据对象的地址和Java内存机制完成了上述的要求,但这通常也不是我们想要的,我们还是需要重写hashCode方法来改变等价的判断方法.
@Override
public int hashCode() {
int res = name.hashCode();
res = res*31+ID.hashCode();
return res;
}
和equals方法相同,我们使用了两个关键域的hashCode值的运算来保证我们认为等价的两个对象hashCode值相等.同时我们进行这个乘31并相加的运算,是为了让不等价的对象的hashCode值更趋于不同,虽然这不是必要的,但这一点能够提升HashMap等类的性能.
对于一个不可变类,我们总是需要重写equals方法和hashCode方法来重新定义这个类的等价性,否则在使用一些数据结构(List,HashMap等)时,很有可能遇到一些令人迷惑的错误.
可变类
直接或间接地使用不可变类的hashCode方法并不安全,举例来说,下面的HashSet<ArrayList<String>>展现出了很令人迷惑的行为
HashSet<ArrayList<String>> listSet = new HashSet<>();
ArrayList<String> l1 = new ArrayList<>();
l1.add("test");
listSet.add(l1);
System.out.println(listSet.contains(l1));
l1.add("test2");
System.out.println(listSet.contains(l1));
我们可以查看ArrayList的hashCode方法,发现它的返回值是受每个元素影响的.
也就是说,在第二次向l1中插入串后,l1的hashCode值就改变了,再次调用listSet.contains方法时,使用l1新的hashCode值去寻找,结果就是什么都找不到.
根据上面的例子,我们在使用可变类时最好不要直接或间接地使用它的hashCode值,对我们自己定义的可变类来说,也就没有必要特别设计它的hashCode和equals方法了.在需要对比可变类时,可以使用更复杂而更具体的方法.