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()を実装することは不可能です。
人々がサブクラス化できるようにするつもりのクラスを書くとき、各メソッドがどのように振る舞うべきかについてのコントラクトを書くことは本当に良い考えです。さらに良いのは、人々が契約に違反していないことを証明するためにオーバーライドされたメソッドの実装に対して実行できる単体テストのセットです。仕事が多すぎるので、ほとんど誰もそれをしません。しかし、気にするなら、それはやるべきことです。
よく記述された契約の好例はComparatorです。.equals()
上記の理由で、それが言うことを無視してください。ここだコンパレータは、物事を行うことができる方法の一例.equals()
することはできませんが。
ノート
Josh Blochの「Effective Java」Item 8がこの例のソースでしたが、Blochは3番目の軸の代わりに色を追加し、intの代わりにdoubleを使用するColorPointを使用します。BlochのJavaの例は、基本的にOdersky / Spoon / Vennersによって複製されており、彼らは彼らの例をオンラインで公開しました。
親クラスにサブクラスについて知らせると、この問題を修正できるため、この例に反対する人が何人かいます。サブクラスの数が十分に少なく、親がそれらのすべてを知っている場合、それは事実です。しかし、最初の質問は、他の誰かがサブクラスを作成するAPIを作成することでした。その場合、通常、親実装をサブクラスと互換性を持つように更新することはできません。
ボーナス
コンパレータは、equals()を正しく実装する問題を回避するため、興味深いものです。さらに良いことに、このタイプの継承の問題を修正するためのパターン、つまり戦略設計パターンに従います。HaskellとScalaの人々が興奮するTypeclassは、Strategyパターンでもあります。継承は悪いことでも間違っていることでもありません。注意が必要です。詳細については、Philip Wadlerの論文「アドホックな多型をアドホックにしない方法」を参照してください。