《Effective Java》
equals和hashCode方法是Object类中定义的两个方法:
public native int hashCode();
public boolean equals(Object obj) {
return (this == obj);
}
本文就这两个方法谈谈它们的作用与联系。
1 equals方法
当我们要比较两个数是否相等时,常常通过==
操作符便可得出,但对于两个对象的比较而言,使用==
仅仅是比较该两个对象引用是否指向同一个对象,并未涉及对象中成员的比较,这种比较也叫做“浅比较(Shallow Comparison)”;而要实现“深比较(Deep Comparison)”,则要通过覆盖Object中的equals方法了。
但Object中的非final方法都有着明确的通用约定,在覆盖这些方法时,我们是有责任遵循这些约定的,否则,依赖于这些约定的类或方法就无法获得期望的结果。
覆盖equals方法时,需要遵循的约定有:
- 自反性(reflexive)。对于任何非null的引用值x,x.equals(x)必须返回true。
- 对称性(symmetric)。对于任何非null的引用值x和y,当且仅当y.equals(x)返回true时,x.equals(y)也必须返回true。
- 传递性(transitive)。对于任何非null的引用值x、y和z,如果x.equals(y)返回true,并且y.equals(z)也返回true,那么x.equals(z)也必须返回true。
- 一致性(consistent)。对于任何非null的引用值x和y,只要equals的比较操作在对象中所用的信息没有被修改,多次调用x.equals(y)应付一致地返回true,或者一致地返回false。
- 对于任何非null的引用值x,x.equals(null)必须返回false。
public class Point {
public int x;
public int y;
public Point() {
}
public Point(int x, int y) {
this.x = x;
this.y = y;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (!(obj instanceof Point))
return false;
Point other = (Point) obj;
if (x != other.x)
return false;
if (y != other.y)
return false;
return true;
}
public static void main(String[] args) {
Point p = new Point(1, 1);
Point p2 = new Point(1, 1);
System.out.println(p.equals(p2));//输出true
List<Point> list = new ArrayList<>();
list.add(p);
System.out.println(list.contains(p2));//输出true
}
}
2 hashCode方法
hashCode方法会返回对象的一个哈希值,该方法被用于支持基于哈希的集合(如HashMap,HashSet,Hashtable)运作得更好。在覆盖了equals方法的类中,通常也被要求覆盖hashCode方法。
在覆盖hashCode方法时,需要遵循的通用约定有:
- 在应用程序的执行期间,只要对象的equals方法的比较所用到的信息没有被 修改,那么对同一个对象调用多次,hashCode方法都必须始终如一地返回同一个整数。
- 如果两个对象根据equals(Object )方法比较是相等的,那么调用这两个对象中任意一个对象的hashCode方法都必须产生同样的整数结果
- 如果两个对象根据equals(Object)方法比较是不相等的,那么调用这两个对象中任意一个对象的hashCode方法,则不一定要产生不同的整数结果。但给不相等的对象产生不同的结果,有可能提高哈希列表的性能。
在使用HashSet时,如果我们覆盖了equals方法却没有覆盖hashCode方法时会发生什么呢?
Set<Point> set = new HashSet<>();
set.add(p);
set.add(p2);
System.out.println(set.size());//输出2
我们都知道,HashSet存储的是不重复的对象,在上面p.equals(p2)返回true的情况下,p和p2都成功添加了进去。
那么,在我们覆盖hashCode方法之后呢,该方法使用eclipse工具生成:
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + x;
result = prime * result + y;
return result;
}
再次运行,可以看到结果输出是1,HashSet是保持了“不重复的”特性。
3 注意点
- 我们没有办法在扩展可实例化的类的同时,既增加新的属性成员,同时又遵循equals通用约定,除非愿意放弃面向对象的抽象所带来的优势,或者使用组合而非继承。
- 覆盖equals方法时,不可以把参数的Object类型换为更具体的类型,这样的做法是重载而不是覆盖。
- 覆盖时hashCode方法时须排除equals方法比较时没有用到的属性,否则会违反约定的第二条。