多層クラス構造のオブジェクト等化


にある
Effective Javaでは、Joshua Blochは、インスタンス化可能なクラスがequalsメソッドを定義している場合に言及しています.もう1つのサブクラスはそれを継承し、追加の属性も定義し、equalsメソッドではこれらの新しい定義の属性を使用して等性判断を行う必要があります.ではequalsの意味の正しさを保証することはできない.
信じて見た
Effective Javaの人は、ここまで読んでがっかりしました.まるで完璧な世界に突然縫い合わせることができない割れ目があったようだ.まず興味を完全に失わないで、次の文章を見てください.
How to Write an Equality Method in Java
この主にScalaの著者Martin Oderskyが執筆した文章には興味深い方法がある.各クラスはequalsを定義する際に,まずcanequalが検証できるかどうかを判断する.canequalの役割は、比較されたオブジェクトが現在のオブジェクトのサブクラスまたは同類である場合にのみ通過することを制限することです.

class Point {

  //     
  ...

  boolean canEqual(Object other) {
    return (other instanceof Point);
  }
  
  @Override boolean equals(Object other) {
    if (other instanceof Point) {
      Point that = (Point) other;
      if (that.canEqual(this) && getX() == that.getX() && getY() == that.getY()) return true;
    }
    return false; 
  }
  
}

子クラスの定義は親クラスと似ています.

class ColoredPoint extends Point {

  //        
  ...

  boolean canEqual(Object other) {
    return (other instanceof ColoredPoint);
  }
  
  @Override boolean equals(Object other) {
    if (other instanceof ColoredPoint) {
      ColoredPoint that = (Point) other;
      if (that.canEqual(this) && color.equals(that.color) && super.equals(that)) return true;
    }
    return false; 
  }
  
}

すなわち、このような約束の下で、canequalを再ロードしたサブクラスのインスタンスと親クラスのインスタンスを比較するとfalseが返されるに違いない.この文章について、興味があればそれに応じて読んでください.
ディスカッション .
Liskov Substitution Principle(LSP)に違反しているかどうか,および違反している場合にこの問題がどれほど深刻かに焦点を当てた.次の分析を見て、このような議論はあまり意味がないと思うかもしれません.
個人的にはこのcanEqualの方法をお勧めします.私が「方法」と言って「解決策」と言わないのは、Oderskyが記述したequals実装がBlochが本来望んでいたequals論理モデルと一致しないと思うからだ.Oderskyの文章の例を想像します.クラス-ポイント(Point)とそのサブクラス-カラーポイント(ColoredPoint)があります.カラーポイントインスタンスの場合、その座標は一般的なポイント座標と同じであり、カラーポイント「Yes」ポイントのため、この2つのポイントは「等しい」必要があります.このような結論が成立することを期待しているので、Blochの結論を見ると、対象に固有の矛盾点があると感じます.しかし、このような結論は天然に成立したわけではない.色のない点と色のある点は等しくなりますか?ColoredPointのカラー属性が列挙され、そのサブクラスがColor.UNSPECIFIED(指定されていない色)にインスタンス化されている場合、この2つの点は論理的に等しいのではないでしょうか.ColoredPoint.colorがこのような属性値を持つことができれば、Pointクラスは抽象クラスとして定義されるべきだと思います.Pointクラスのインスタンス化は意味がありません.言い換えれば、Pointクラスがインスタンス化され、そのサブクラスColoredPointにも「指定されていない色」があり、両方ともequalsが定義されている場合、設計に失敗したと思います.
LSPを見てみましょう.LSPは、親インスタンスを使用することができる任意の場所で、子インスタンスを使用することができると述べている.ここではLSPに違反しません.なぜなら、ある場所でこのように呼び出すことができる場合、

Point p = new Point();
if (p.equals(...)) {
  ...
}

では、サブクラスと同様にequalsを呼び出すことができます.ただし、equalsが同じパラメータを入力したときに返される結果は異なる場合があります.しかし、LSPは同じ結果を返さなければならないという制約はありません.これが多態の特徴です
Blochの論点に戻ります.今私に賛成している人はBlochの論点に問題があると思うかもしれません.実は彼の言うことは厳格で,少しも問題がない.彼の論点の前提は、インスタンス化可能な親である.すなわち,非抽象クラスに対して,みんなの伝統的な期待を満たすサブクラスを書くことはできない.ただ、他の人はがっかりして、彼はこの結論を出した後、対応の方法を提供しなかった.対照的にOderskyはBlochの論理モデルを整理した.従って、Oderskyが発明したScalaにおいても、canequalという方法は、公式に推奨されるequals実装方法として用いられる.