equals一般的な約束を遵守し、再定義
37071 ワード
equalsは誤って再定義すると恐ろしい結果をもたらす。したがって、次のいずれかに該当する場合は、再定義しないほうがよい。
@Override
public boolean equals(Object a){
throw new AssertionError(); 호출 금지
}
では、equalsを再定義するのはいつですか?
論理整合性ではなく、オブジェクト識別性(objectidentity:2つのオブジェクトが物理的に同じかどうかを確認する必要があります.親クラスのequalsを再定義して論理整合性を比較しない場合.主に価値のある類です.値クラスとは、IntegerやStringのように値を表すクラスです.
equalsメソッドを再定義する場合は、一般的な約束に従う必要があります。
equalsメソッドは等値関係(equals relation)を実現し、以下の条件を満たす.
→同治関係って何?簡単に言えば、集合を同じ要素からなる部分集合に分ける演算である.この一致関係を満たすために、次は5つの要求を1つずつ見てみましょう.
import java.util.Objects;
public class CaseInsesitiveString {
private final String s;
public CaseInsesitiveString(String s) {
this.s = Objects.requireNonNull(s);
}
@Override
public boolean equals(Object o) {
if(o instanceof CaseInsesitiveString)
return s.equalsIgnoreCase(
((CaseInsesitiveString) o).s);
if(o instanceof String)
return s.equalsIgnoreCase((String) o);
return false;
}
}
public class Main {
public static void main(String[] args) {
CaseInsesitiveString cis = new CaseInsesitiveString("Hello");
String s = "hello";
System.out.println(s.equals(cis));
System.out.println(cis.equals(s));
}
}
실행결과
false
true
このコードの問題は,CaseInsensitiveStringのequalsは通常Stringを知っているが,StringのequalsはCaseInsensitiveStringの存在を知らないことである.従って,s.equals(cis)はfalseを返し,対称性に著しく違反した.この問題を解決するには、CaseInsensitiveStringのequalsとStringを結びつけるでたらめな夢を放棄しなければならない.
if(o instanceof String)
return s.equalsIgnoreCase((String) o);
return false;
したがって、CaseInsistiveStringクラスのequalsメソッドから上記の部分を削除する必要があります.まず,矩形の横と縦を表すクラスを以下に示す.
public class Point {
private final int x;
private final int y;
public Point(int x, int y) {
this.x = x;
this.y = y;
}
@Override
public boolean equals(Object o) {
if (!(o instanceof Point))
return false;
Point p = (Square)o;
return p.x == x && p.y == y;
}
}
クラスを展開し、長方形に色を追加します.public class ColorPoint extends Point{
private final Color color;
public ColorPoint(int x, int y, Color color) {
super(x, y);
this.color = color;
}
}
equalsメソッドをそのままにすると、Squareの実装が継承され、色情報を無視して比較が実行されます.以下のコードに示すように、比較対象が別の
ColorPoint
であり、位置と色が同じである場合にのみtrueのequalsが返されることを考慮してください.@Override
public boolean equals(Object o){
if(!(o instanceof ColorPoint))
return false;
return super.equals(o) && ((ColorPoint) o).color == color;
}
このメソッドはfalseを返すたびに、Squareのequalsは色を無視し、CoorsquareのequalsはSquareオブジェクトを受け入れないためです.では次のようにColorSquareequalsとSquareを比較する場合、色を無視して解決できますか?
@Override
public boolean equals(Object o){
if(!(o instanceof ColorSquare))
return false;
if(!(o instanceof ColorSquare))
return o.equals(this);
return super.equals(o) && ((ColorSquare) o).color == color;
}
この方式は対称性を保っているが,同性化を破った.public static void main(String[] args) {
ColorPoint p1 = new ColorPoint(1, 2, Color.RED);
Point p2 = new Point(1, 2);
ColorPoint p3 = new ColorPoint(1, 2, Color.BLUE);
System.out.println(p1.equals(p2));
System.out.println(p2.equals(p3));
System.out.println(p1.equals(p3));
}
실행결과
true
true
false
では、解決策は何でしょうか.これはすべてのオブジェクト向け言語の同治関係に現れる根植的な問題である.拡張特定のクラスに新しい値を追加する場合、equals約定を満たす方法は存在しません.オブジェクト向けの抽象化のメリットを放棄しない限り.equalsのinstanceofチェックをgetClassチェックに変更すると、約束を守り、値を追加して特定のクラスを継承できるという意味です.
ColorSquare.equalsメソッドを
@Override
public boolean equals(Object o){
if(o == null || o.getClass() != this.getClass())
return false;
Point p = (Point) o;
return p.getX() == this.getX() && p.getY() == this.getY();
}
実行public static void main(String[] args) {
ColorPoint p1 = new ColorPoint(1, 2, Color.RED);
Point p2 = new Point(1, 2);
ColorPoint p3 = new ColorPoint(1, 2, Color.BLUE);
System.out.println(p1.equals(p2));
System.out.println(p2.equals(p3));
System.out.println(p1.equals(p3));
}
실행 결과
false
true
true
今回は、同じインプリメンテーションクラスのオブジェクトと比較した場合にのみtrueを返します.getClassを使用する場合、リスク切替の原則(=任意のタイプにとって、重要な属性はそのサブタイプでも重要です).背くことができる.すなわち,Pointのサブクラスは定義上は依然としてPointであるため,どこでもPointとして使用すべきである.(したがって、継承関係はinstanceofを使用する必要があります.)
例えば、与えられた点が単位円内(半径1)であるかどうかを決定する方法が必要であると仮定する.
public class Point {
...
...
private static final Set<Point> unitCircle = Set.of(
new Point(1, 0), new Point(0, 1),
new Point(-1, 0), new Point(0, -1)
);
public static boolean onUnitCircle(Point p) {
return unitCircle.contains(p);
}
}
CounterPointpublic class CounterPoint extends Point {
private static final AtomicInteger counter = new AtomicInteger();
public CounterPoint(int x, int y) {
super(x, y);
counter.incrementAndGet();
}
public static int numberCreated() {
return counter.get();
}
}
public class Main {
public static void main(String[] args) {
Point p = new Point(1, 0);
CounterPoint.onUnitCircle(p);
System.out.println(CounterPoint.numberCreated());
Point cp = new CounterPoint(1, 0);
CounterPoint.onUnitCircle(cp);
System.out.println(CounterPoint.numberCreated());
}
}
실행결과
0
1
特定のクラスのサブクラスに値を追加することはできませんが、良い迂回方法があります.「継承の代わりに複合語を使用する」項目18の提案に従えばよい.ポイントを継承する代わりにColorPointのprivateフィールドとして使用し、ColorPointなどのビューメソッドをpublicに追加します.クラスが不変であろうと可変であろうと,equalsの判断に信頼できないリソースを介入させることはできない.この制約に違反すると,コンシステンシ条件を満たすことは困難である.たとえばjavaです.net.URLのequalsは,所与のURLとマッピングホストのIPアドレスを用いて比較する.ホスト名をIPアドレスに変更するには、ネットワークを通過する必要がありますが、結果が常に同じであることは保証されません.これによりURLのequalsは一般的な約束に違反し,実際の作業でも問題を引き起こすことが多い.
このような問題を回避するには、equalsはメモリに存在するオブジェクトのみを使用して決定的な計算を行うことができます.
o.equals(null)がtrueを返すことは想像しにくいが,nullPointerExceptionのコードをうっかり投げ出すのはよくある.この一般的なルールはこの場合も許されない.多くのクラスはnullと入力して自分を保護します.
public boolean equals(Object o){
if(o == null){
return false
}
...
}
この検査は必要ありません.instanceof(2番目の被演算子と罪のない)最初の被演算子がnullの場合falseが返されます.Reference
この問題について(equals一般的な約束を遵守し、再定義), 我々は、より多くの情報をここで見つけました https://velog.io/@eastshine-high/equals는-일반-규약을-지켜-재정의하라テキストは自由に共有またはコピーできます。ただし、このドキュメントのURLは参考URLとして残しておいてください。
Collection and Share based on the CC Protocol