我有以下两个非常简单的类:
public class A {
private int a;
public A(int a)
{
this.a=a;
}
public int getA(){
return a;
}
public boolean equals(Object o)
{
if (!(o instanceof A))
return false;
A other = (A) o;
return a == other.a;
}
}
它的子类:
public class B extends A{
private int b;
public B(int a, int b)
{
super(a);
this.b = b;
}
public boolean equals(Object o)
{
if (!(o instanceof B))
return false;
B other = (B) o;
return super.getA() == other.getA() && b == other.b;
}
}
这一开始似乎是正确的,但在以下情况下它违反了规范中对象的普遍契约的对称原则,该原则规定:
"它是对称的:对于任何非空引用值x和y,当且仅当y.equals(x)返回true时,x.equals(y)应返回true。" http://docs.oracle.com/javase/7/docs/api/java/lang/Object.html#equals(java.lang.Object)
失败的情况如下:
public class EqualsTester {
public static void main(String[] args) {
A a = new A(1);
B b = new B(1,2);
System.out.println(a.equals(b));
System.out.println(b.equals(a));
}
}
第一个返回true,而第二个返回false。 一个看起来正确的解决方案是使用
getClass()
代替instanceof
。但是,在我们查找集合中的B的情况下,这种方法是不可接受的。例如:Set<A> set = new HashSet<A>();
set.add(new A(1));
调用
set.contains(new B(1,2));
方法会返回false。尽管这个例子在逻辑上可能不太容易理解,但可以想象一下A类是一个车辆类,B类是一个汽车类,其中字段a表示轮子数,字段b表示车门数。当我们调用contains方法时,实质上是在询问:“我们的集合中是否包含一辆四轮车?”答案应该是肯定的,因为它确实包含了,而不管它是不是一辆汽车以及它有多少车门。
Joshua Block在Effective Java 2nd Ed pg 40中提出的解决方案是不要让B从A继承,并在B中拥有一个A的实例作为字段。public class B {
private A a;
private int b;
public B(int a, int b)
{
this.a = new A(a);
this.b = b;
}
public A getAsA()
{
return A;
}
public boolean equals(Object o)
{
if (!(o instanceof B))
return false;
B other = (B) o;
return a.getA() == other.getA() && b == other.b;
}
}
然而,这是否意味着我们在许多需要使用继承的情况下失去了使用它的能力?也就是说,当我们有一些带有额外属性的类需要扩展更通用的类以重用代码并添加它们自己的特定属性时。