equals 方法在非空对象引用上实现相等关系:
自反性:对于任何非空引用值 x,x.equals(x) 都应返回 true。
对称性:对于任何非空引用值 x 和 y,当且仅当 y.equals(x) 返回 true 时,x.equals(y) 才应返回 true。
传递性:对于任何非空引用值 x、y 和 z,如果 x.equals(y) 返回 true,并且 y.equals(z) 返回 true,那么 x.equals(z) 应返回 true。
一致性:对于任何非空引用值 x 和 y,多次调用 x.equals(y) 始终返回 true 或始终返回 false,前提是对象上 equals 比较中所用的信息没有被修改。
对于任何非空引用值 x,x.equals(null) 都应返回 false。
Object 类的 equals 方法实现对象上差别可能性最大的相等关系;即,对于任何非空引用值 x 和 y,当且仅当 x 和 y 引用同一个对象时,此方法才返回 true(x == y 具有值 true)。
注意:当此方法被重写时,通常有必要重写 hashCode 方法,以维护 hashCode 方法的常规协定,该协定声明相等对象必须具有相等的哈希码。
参数:
obj - 要与之比较的引用对象。
返回:
如果此对象与 obj 参数相同,则返回 true;否则返回 false。
另请参见:
hashCode(), Hashtable
我们先来看看Object中equals的实现:
public boolean equals(Object obj) {
return (this == obj);
}
当且仅当两个对象是同一个时,equals才返回true,这个实现当然满足以上的自反性、对称性、传递性、一致性。然而在绝大多数情况下,这个默认实现都不好用,用户往往需要重写这个方法。一旦重写了这个方法,很可能潜在的破坏了以上约束,给程序带来潜在威胁。
看一个Effective java中给出的一个违反对称性的例子:
/**
* Case-insensitive string. Case of the original string is preserved by
* toString, but ignored in comparisons.
*/
public final class CaseInsensitiveString {
private String s;
public CaseInsensitiveString(String s) {
if (s == null)
throw new NullPointerException();
this.s = s;
}
// Broken - violates symmetry!
public boolean equals(Object o) {
if (o instanceof CaseInsensitiveString)
return s.equalsIgnoreCase(((CaseInsensitiveString) o).s);
if (o instanceof String) // One-way interoperability!
return s.equalsIgnoreCase((String) o);
return false;
}
// Remainder omitted
}
这个实现对于
CaseInsensitiveString cis = new CaseInsensitiveString("Polish");
String s = "polish";
cis.equals(s) 为true,而s.equals(cis) 为false。如果出现下面的代码,结果是什么呢?
List list = new ArrayList();
list.add(cis);
list.contains(s) = ?
到底list.contains(s)返回true还是false,这就说不清了。按照目前的JVM实现,这个是false。
一个可能的实现为:
public boolean equals(Object o) {
return o instanceof CaseInsensitiveString
&& ((CaseInsensitiveString) o).s.equalsIgnoreCase(s);
}
再看一个违反传递性的例子:
public class Point {
private final int x;
private final int y;
public Point(int x, int y) {
this.x = x;
this.y = y;
}
public boolean equals(Object o) {
if (!(o instanceof Point))
return false;
Point p = (Point) o;
return p.x == x && p.y == y;
}
}
public class ColorPoint extends Point {
private Color color;
public ColorPoint(int x, int y, Color color) {
super(x, y);
this.color = color;
}
// Broken - violates transitivity.
public boolean equals(Object o) {
if (!(o instanceof Point))
return false;
// If o is a normal Point, do a color-blind comparison
if (!(o instanceof ColorPoint))
return o.equals(this);
// o is a ColorPoint; do a full comparison
ColorPoint cp = (ColorPoint) o;
return super.equals(o) && cp.color == color;
}
}
根据上面的实现
ColorPoint p1 = new ColorPoint(1, 2, Color.RED);
Point p2 = new Point(1, 2);
ColorPoint p3 = new ColorPoint(1, 2, Color.BLUE);
p1.equals(p2) = ture && p2.equals(p3) = true,而p1.equals(p3) = false,原因很简单,在
if (!(o instanceof ColorPoint))
return o.equals(this);
这里出了问题,当o为Point时,调用了Point的equals(),忽略了color属性,从而使得p1.equals(p2)&&p2.equals(p3),一个可能的实现为
// Adds an aspect without violating the equals contract
public class ColorPoint {
private Point point;
private Color color;
public ColorPoint(int x, int y, Color color) {
point = new Point(x, y);
this.color = color;
}
/**
* Returns the point-view of this color point.
*/
public Point asPoint() {
return point;
}
public boolean equals(Object o) {
if (!(o instanceof ColorPoint))
return false;
ColorPoint cp = (ColorPoint)o;
return cp.point.equals(point) && cp.color.equals(color);
}
... // Remainder omitted
}
在重写扩展基类的equals方法时,最好放弃使用继承,而采取组成的方式,将基类作为一个成员属性,这样可以避免由继承带来的各种问题。There is simply no way to extend an instantiable class with a new aspect while preserving the compareTo contract。如果你想扩展一个已经实现了equals的类,不要去继承它,而是在类中使用一个它的实例对象作为成员。
接下来讲讲 == 操作符与equals()的关系。== 比较两个对象时,返回true意味着两个对象是同一个。在Object的默认实现中,== 与equals就是一回事,也就是
Object obj1, obj2;
obj1.equals(obj2) 与obj1 == obj2 是一样的效果, 但是要注意在obj1、obj2都为null时,前者会抛出异常而后者则返回true。
Object obj1 = null, obj2 = null;
if(obj1 == obj2){
System.out.println("true");
}else{
System.out.println("false");
}
if(obj1.equals(obj2)){
System.out.println("true");
}else{
System.out.println("false");
}
}
输出为:
Exception in thread "main" java.lang.NullPointerException
参考文档:
1,《Efficient Java》 Joshua Bloch
2, J2sk 6.0 API