2015-10-04 8 views
10

彼の本有効なJavaで、Joshua Blochは、派生クラスがチェックにフィールドを追加するときにequals()の契約で発生する落とし穴について書いています。通常、これは対称性を破るでしょうが、 "の場合、equals規約に違反することなく抽象クラスのサブクラスに値コンポーネントを追加します"と述べています。抽象クラスから派生した場合のequals()の継承方法

明示的には抽象クラスのインスタンスが存在しない可能性があるので、これは当てはまります。したがって、違反する対称性はありません。しかし、他のサブクラスはどうですか?私は、同じ色の列と、各クラスの1つのインスタンスを作成すると、equals()の対称性が破壊され

public abstract class Vehicle { 

    private final String color; 

    public Vehicle(String color) { 
     this.color = color; 
    } 

    public String getColor() { 
     return color; 
    } 

    @Override 
    public boolean equals(Object o) { 
     if (this == o) return true; 

     if (!(o instanceof Vehicle)) return false; 

     Vehicle that = (Vehicle) o; 

     return color.equals(that.color); 
    } 

} 

public class Bicycle extends Vehicle { 

    public Bicycle(String color) { 
     super(color); 
    } 

} 

public class Car extends Vehicle { 

    private final String model; 

    public Car(String color, String model) { 
     super(color); 
     this.model = model; 
    } 

    @Override 
    public boolean equals(Object o) { 
     if (this == o) return true; 

     if (!(o instanceof Car)) return false; 

     Car that = (Car) o; 

     return getColor().equals(that.getColor()) && model.equals(that.model); 
    } 

} 

:私は意図的に短いコードを維持するためにハッシュコードの実装及びヌルチェックを省略し、本実施例を書い

Bicycle bicycle = new Bicycle("blue"); 
Car car = new Car("blue", "Mercedes"); 

bicycle.equals(car) <- true 
car.equals(bicycle) <- false 

これを処理する方法がわかりません。 equals()を抽象クラスの抽象クラスとして宣言して、サブクラスでの実装を強制しますか?しかし、抽象クラスではequals()をまったく宣言しないと同じ効果が得られます。

+0

「o」が「o instanceof Car」の「自転車」のインスタンスである場合はどうなりますか? – Hannes

+0

これは誤りです。問題は、抽象スーパークラスの 'equals()'メソッドで 'Car'インスタンスがチェックされるときのもう一つの方向です。 –

+0

「あなたが同じ色の文字列を持つ各クラスのインスタンスを1つ作成すると、equals()の対称性が壊れます。」 – wero

答えて

2

Javaの等価規約は、このような状況で特に不安定になり、最終的にはすべてプログラマの好みやニーズの問題になります。私はこの同じ問題にぶつかったことを覚えています。私はthis articleに出くわしました。これは、Javaとの等価契約を検討する際にいくつかの可能性と問題点があります。それは基本的には、Javaと同等の契約を破ることなく適切に行う方法がないということに終わります。

抽象のクラスを扱う場合、私の個人的な好みは抽象クラスにequalsメソッドをまったく与えないことです。それは意味をなさない。抽象型の2つのオブジェクトを持つことはできません。どのようにそれらを比較する必要がありますか?代わりに、私は各サブクラスにそれ自身のequalsを与え、equals()が呼び出されるたびにランタイムは残りを処理します。そして、全体として、私が最も頻繁に従う記事に提示されている解決策は、「正確に同じクラスの唯一のオブジェクトが比較されるかもしれない」ということです。これは私にとって最も賢明なようです。

+0

"正確に同じクラス"の制限は、実際にはほとんどのプログラマがクラスの動作を期待する方法ですか?私はこれについて確信しています。たとえば、 'java.sql.Date'とします。これは 'equals()'をオーバーライドせずに 'java.util.Date'から継承します。彼らは同じ時点を参照する限り、彼らは等しいとみなされます。 –

+0

@StephanWindmüllerという難しい部分があります。 equalsを使用する場合、柔軟性を持たなければなりません。いくつかの規則を乱さずに、サブクラスのオーバーライドを使ってequals契約を100%制定することは非常に困難です。日付のようなケースでは、異なる方法で同じことを意味するので、私は上書きしません。しかし、サブクラスが親との重要な違いを持つ他のものについては、私は同じ型のequalsのみを使用します。 – BHustus

+0

しかし、 'java.util.Date'の開発者が' getClass() 'を使って' equals() 'を選択した場合、現在の動作でサブクラスを作成することはできませんでした。それを要約すると、私の質問に対する答えは「それは依存している」ようです。あなたの答えは得られるほど近いので、正しいものとしてマークしました。 –

1

instanceofチェックを行う代わりにクラスオブジェクトを比較すると、問題が解決されます。ここ

 if (getClass() != obj.getClass()) { 
      return false; 
     } 

は(エクリプスによって生成された)完全な実装である:

public class Vehicle { 

    // ...  

    @Override 
    public boolean equals(Object obj) { 
     if (this == obj) { 
      return true; 
     } 
     if (obj == null) { 
      return false; 
     } 
     if (getClass() != obj.getClass()) { 
      return false; 
     } 
     Vehicle other = (Vehicle) obj; 
     if (color == null) { 
      if (other.color != null) { 
       return false; 
      } 
     } else if (!color.equals(other.color)) { 
      return false; 
     } 
     return true; 
    } 

} 

public class Car extends Vehicle { 

    // ... 

    @Override 
    public boolean equals(Object obj) { 
     if (this == obj) { 
      return true; 
     } 
     if (!super.equals(obj)) { 
      return false; 
     } 
     if (getClass() != obj.getClass()) { 
      return false; 
     } 
     Car other = (Car) obj; 
     if (model == null) { 
      if (other.model != null) { 
       return false; 
      } 
     } else if (!model.equals(other.model)) { 
      return false; 
     } 
     return true; 
    } 


} 

両方のチェックはあなたの例では、次にfalseをもたらします。

+0

equalsメソッドは、最初にオブジェクトの型が同じであるかどうかを、その前に 'super.equals(obj)'を呼び出すのではなくチェックする必要があります。 2つのオブジェクトが同じ型でなくても、スーパークラスequals()メソッドが不必要に呼び出されます。 – YoungHobbit

+0

'getClass()'を使うと、問題をシフトするだけです: 'Car'の別のサブクラスを想像してください。同じクラスではないので、' Car'と決して同じではありません。また、生成されたコードは、Vehicleが抽象であるという事実を尊重しません。 –

+0

@StephanWindmüllerこれはあなたのユースケースと同等の意見に依存します。プロキシ・サブクラスを作成するHibernateなどのフレームワークを使用する場合は、必要になる可能性があります。それ以外は、2つのオブジェクトが等しいが異なるタイプのオブジェクトであれば直感的ではないと思います。 –

0

equals()は、基本的にオブジェクトの状態を比較します。抽象クラスを作成するときは、オブジェクトの状態をどのように扱うかについて考える必要があります。

あなたの場合、車両は特定の色の状態にあります。質問は:同じ色の車両はすべて同じに扱いたいですか?次に、この質問に対する答えを抽象クラスの契約の一部にしてください。

あなたはYESと答えた場合:

十分に簡単、ちょうど最終等しいします。

あなたはNO答える場合:

あなたは(非常に当然のことながら)あなたのequalsは対称になりたいです。以下のコードを見てみましょう:

Bicycle bike = new Bicycle("blue"); 
Car car = new Car("blue", "gtr"); 
assert car.equals(bike) == bike.equals(car); 

bike.equals(car)を呼び出すときに、あなたが自転車の基準で比較しているので、これは、AssertionErrorがで失敗します。これを解決するには、自転車にequalsを実装することができます。しかし、誰かがどこかでequalsを実装することを忘れないようにするために、すべてのクラスを調べることは望ましくありません。

この場合、抽象親で、異なるサブクラスがfalseを返すようにしてください。これは、if (! (o instanceof P)) return false;if (!getClass().equals(o.getClass())) return false;に置き換えるだけで簡単に行うことができます。あなたの対称性は保存されます。

+0

'getClass()'を使うと、 'Car'を拡張するクラスのような、追加のサブクラスでは複雑になります。 「トラック」。 「トラック」は、同じクラスではないので、決して「車」と同等ではありません。 –

+0

これは実装方法によって異なります。例えば ​​'Truck'は、あなたの' Car'が 'instanceof'と等しい点を実装していれば等しくなります。これは' Vehicle'の 'getClass()'をオーバーライドします。基本的には、クラス階層のどのレベルでも同じ質問をするだけです。 – thegeko

1

equals()の対称性は、Bicycleクラスがサブクラスであり、スーパークラス(Vehicle)に依存しているため、主に壊れています。サブクラスごとにequals()メソッドを定義すると、この問題は発生しません。

各クラスの実装はequals()です。 (のみBicycleequals()が追加され、他の実装は同じですが、簡略化されている。)

public abstract class Vehicle { 
.... 
     @Override 
    public boolean equals(Object o) { 
    if (this == o) return true; 
    if (!(o instanceof Vehicle)) return false; 
    Vehicle that = (Vehicle) o; 
    return color.equals(that.color); 
    } 
} 

public class Bicycle extends Vehicle { 
... 
    @Override 
    public boolean equals(Object o) { 
    if (this == o) return true; 
    if (!(o instanceof Bicycle)) return false; 
    Bicycle that = (Bicycle) o; 
    return super.getColor().equals(that.getColor()); 
    } 
} 

public class Car extends Vehicle { 
... 
    @Override 
    public boolean equals(Object o) { 
    if (this == o) return true; 
    if (!(o instanceof Car)) return false; 
    if (!super.equals(o)) return false; 
    Car car = (Car) o; 
    return model.equals(car.model); 
    } 
} 

// This is main class for testing the above functionality. 

class MainClass { 
    public static void main(String[] args) { 
    Bicycle bicycle = new Bicycle("blue"); 
    Car car = new Car("blue", "Mercedes"); 

    System.out.println(bicycle.equals(car)); -> false 
    System.out.println(car.equals(bicycle)); -> false 
    } 
} 

OR @FranzBeckerにより示唆されるように、あなたのスーパークラスの実装に代わりinstanceofオペレータのobject.getClass()を使用する必要があります。サブクラスは引き続き問題なくinstanceOf演算子を使用できます。

+0

私はあなたの答えの最後の部分について不明です。抽象クラスでの 'getClass()'の使い方は意味がありますか?私はこのクラスのオブジェクトを作成することができません。 –

+0

@StephanWindmüllerそうです。しかし、それはスーパークラス(Vehicle)に従属するサブクラス(例えば、自転車)です。それが唯一の理由です。答えで述べたように、各サブクラスに対して 'equals()'を定義すると、必須ではありません。また、サブクラスequalsは、継承されたメンバ変数のためにスーパークラス 'equals()'を呼び出すことに注意してください。他の点では、それぞれの実装で処理する必要があります。 – YoungHobbit

+0

@StephanWindmüller抽象クラスで 'getClass()'を呼び出すと、具体的なインスタンスのクラスオブジェクトが返されます。したがって、 'Bicycle'の場合、抽象クラスのメソッド呼び出しのコードは' Vehicle.class'ではなく 'Bicycle.class'を返します。 –

関連する問題