Kilian Foth的答案非常好。我只想添加关于为什么这是一个问题的规范示例。想象一个整数Point类:
class Point2D {
public int x;
public int y;
// constructor
public Point2D(int theX, int theY) { x = theX; y = theY; }
public int hashCode() { return x + y; }
public boolean equals(Object o) {
if (this == o) { return true; }
if ( !(o instanceof Point2D) ) { return false; }
Point2D that = (Point2D) o;
return (x == that.x) &&
(y == that.y);
}
}
现在让我们将其子类化为3D点。
class Point3D extends Point2D {
public int z;
// constructor
public Point3D(int theX, int theY, int theZ) {
super(x, y); z = theZ;
}
public int hashCode() { return super.hashCode() + z; }
public boolean equals(Object o) {
if (this == o) { return true; }
if ( !(o instanceof Point3D) ) { return false; }
Point3D that = (Point3D) o;
return super.equals(that) &&
(z == that.z);
}
}
超级简单!让我们使用我们的观点:
Point2D p2a = new Point2D(3, 5);
Point2D p2b = new Point2D(3, 5);
Point2D p2c = new Point2D(3, 7);
p2a.equals(p2b); // true
p2b.equals(p2a); // true
p2a.equals(p2c); // false
Point3D p3a = new Point3D(3, 5, 7);
Point3D p3b = new Point3D(3, 5, 7);
Point3D p3c = new Point3D(3, 7, 11);
p3a.equals(p3b); // true
p3b.equals(p3a); // true
p3a.equals(p3c); // false
您可能想知道为什么我要发布这样一个简单的示例。这是要抓住的地方:
p2a.equals(p3a); // true
p3a.equals(p2a); // FALSE!
当我们将2D点与等效的3D点进行比较时,我们得到了true,但是当我们逆转比较时,我们得到了false(因为p2a失败instanceof Point3D
)。
结论
通常可以以与超类期望它的工作方式不再兼容的方式在子类中实现方法。
通常,不可能以与其父类兼容的方式在明显不同的子类上实现equals()。
当您编写要允许人们继承的类时,对于每个方法的行为写一个契约是一个非常好的主意。更好的是,人们可以对覆盖方法的实现进行一系列的单元测试,以证明他们没有违反合同。几乎没有人这样做,因为它工作太多。但是,如果您在乎,那就是要做的事情。
精心阐述了合同的一个很好的例子比较。.equals()
出于上述原因,只需忽略它所说的内容即可。下面是的比较如何能做到的事情为例.equals()
不了。
笔记
Josh Bloch的“ Effective Java”项目8是此示例的来源,但Bloch使用ColorPoint而不是第三轴添加颜色,并使用double代替int。Odersky / Spoon / Venners基本上复制了Bloch的Java示例,并将其示例在线提供。
有几个人反对此示例,因为如果您让父类知道有关子类的信息,则可以解决此问题。如果子类的数量足够少,并且父级知道所有子类,则为真。但是最初的问题是关于制作其他人将为其编写子类的API。在这种情况下,通常无法将父实现更新为与子类兼容。
奖金
比较器也很有趣,因为它可以解决正确实现equals()的问题。更好的是,它遵循一种用于解决此类继承问题的模式:策略设计模式。Haskell和Scala让人兴奋的Typeclass也是Strategy模式。继承不是坏事,也不是错误的,只是棘手的事情。进一步阅读,请查阅Philip Wadler的论文:如何减少即席多态性