3

http://leepoint.net/notes-java/data/expressions/22compareobjects.html

equals() の定義は簡単ではないことがわかりました。実際、特にサブクラスの場合、それを正しく行うのはやや困難です。この問題の最良の扱いは、Horstmann の Core Java Vol 1 にあります。

equals() を常にオーバーライドする必要がある場合、オブジェクト比較を行う必要に追い込まれないようにするための適切なアプローチは何ですか? 良い「デザイン」の代替案は何ですか?

編集:

これが私が意図した方法で出くわすかどうかはわかりません。おそらく、質問は「なぜ 2 つのオブジェクトを比較したいのか?」に沿ったものであるべきです。その質問に対するあなたの答えに基づいて、比較するための代替ソリューションはありますか? つまり、equals の別の実装です。つまり、平等をまったく使用していません。重要なポイントは、その質問から始めることだと思います。なぜ 2 つのオブジェクトを比較したいのでしょうか。

4

7 に答える 7

4

equals() を常にオーバーライドする必要がある場合、オブジェクト比較を行う必要に追い込まれないようにするための適切なアプローチは何ですか?

あなたは間違っています。equals はできるだけオーバーライドしないでください。


この情報はすべて、Effective Java, Second Edition ( Josh Bloch ) からのものです。これに関する初版の章は、今でも無料でダウンロードできます

効果的な Java から:

問題を回避する最も簡単な方法は、equals メソッドをオーバーライドしないことです。この場合、クラスの各インスタンスはそれ自体とのみ等しくなります。

equals/hashCode を任意にオーバーライドすることの問題は、継承です。一部の equals 実装では、次のようにテストすることを推奨しています。

if (this.getClass() != other.getClass()) {
    return false; //inequal
}

実際、Eclipse (3.4) Java エディターは、ソース ツールを使用してメソッドを生成するときにこれを行います。ブロッホによれば、これはリスコフの置換原理に違反しているため誤りです。

効果的な Java から:

equals コントラクトを維持しながら、インスタンス化可能なクラスを拡張して値コンポーネントを追加する方法はありません。

クラスとインターフェースの章では、等価性の問題を最小限に抑える 2 つの方法について説明します。

  1. 継承よりも構成を優先する
  2. 継承またはそれを禁止するための設計および文書化

私が見る限り、唯一の代替手段は、クラスの外部のフォームで等価性をテストすることであり、それがどのように実行されるかは、型の設計とそれを使用しようとしていたコンテキストによって異なります。

たとえば、比較方法を文書化するインターフェースを定義できます。以下のコードでは、Service インスタンスが実行時に同じクラスの新しいバージョンに置き換えられる可能性があります。その場合、異なる ClassLoaders を使用すると、equals 比較は常に false を返すため、equals/hashCode のオーバーライドは冗長になります。

public class Services {

    private static Map<String, Service> SERVICES = new HashMap<String, Service>();

    static interface Service {
        /** Services with the same name are considered equivalent */
        public String getName();
    }

    public static synchronized void installService(Service service) {
        SERVICES.put(service.getName(), service);
    }

    public static synchronized Service lookup(String name) {
        return SERVICES.get(name);
    }
}

「なぜ 2 つのオブジェクトを比較したいのですか?」

明白な例は、2 つの Strings (または 2 つのFilesまたはURI ) が同じかどうかをテストすることです。たとえば、解析する一連のファイルを構築したい場合はどうでしょう。定義上、セットには一意の要素のみが含まれます。Java のSet型は equals/hashCode メソッドに依存して、その要素の一意性を強制します。

于 2008-12-11T19:28:20.140 に答える
4

equals を常にオーバーライドする必要があるというのは本当ではないと思います。私が理解しているルールは、意味的に同等のオブジェクトを定義する方法が明確な場合にのみ、equals のオーバーライドが意味を持つということです。その場合、 hashCode() もオーバーライドして、異なるハッシュコードを返す同等のものとして定義したオブジェクトを持たないようにします。

意味のある同等性を定義できない場合、私にはメリットがわかりません。

于 2008-12-11T18:12:39.607 に答える
3

それを正しく行うのはどうですか?

これは、Josh Bloch による効果的な Java から適用された知識である私の equals テンプレートです。詳細については、本をお読みください。

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

    // only do this if you are a subclass and care about equals of parent
    if(!super.equals(obj)) {
        return false;
    }
    if(obj == null || getClass() != obj.getClass()) {
        return false;
    }
    final YourTypeHere other = (YourTypeHere) obj;
    if(!instanceMember1.equals(other.instanceMember1)) {
       return false;
     }
     ... rest of instanceMembers in same pattern as above....
     return true;
 }
于 2008-12-11T18:06:27.060 に答える
1

たぶん私は要点を逃していますが、独自のメソッドを別の名前で定義するのではなく equals を使用する唯一の理由は、多くのコレクション (およびおそらく JDK の他のものや最近呼び出されているもの) が equals メソッドを期待しているためです。首尾一貫した結果を定義します。しかし、それ以上に、対等で行いたい 3 種類の比較を考えることができます。

  1. 2 つのオブジェクトは実際には同じインスタンスです。== を使用できるため、equals を使用しても意味がありません。また、Java での動作を忘れた場合は訂正してください。デフォルトの equals メソッドは、自動生成されたハッシュ コードを使用してこれを行います。
  2. 2 つのオブジェクトは同じインスタンスへの参照を持っていますが、同じインスタンスではありません。これは、特に、それらが永続化されたオブジェクトであり、DB 内の同じオブジェクトを参照している場合に便利です。これを行うには、equals メソッドを定義する必要があります。
  3. 2 つのオブジェクトには、値が等しいオブジェクトへの参照がありますが、それらは同じインスタンスである場合もそうでない場合もあります (つまり、階層全体で値を比較します)。

なぜ 2 つのオブジェクトを比較したいのでしょうか? 両者が等しい場合は 1 つのことを行い、そうでない場合は別のことを行います。

とは言っても、それは当面のケースに依存します。

于 2008-12-11T19:19:20.580 に答える
1

うーん

一部のシナリオでは、オブジェクトを変更不可 (読み取り専用) にして、単一のポイントから作成する (ファクトリ メソッド) ことができます。

同じ入力データ (作成パラメーター) を持つ 2 つのオブジェクトが必要な場合、ファクトリは同じインスタンス ref を返し、"==" を使用するだけで十分です。

このアプローチは、特定の状況下でのみ役立ちます。そして、ほとんどの場合、やり過ぎに見えます。

この回答を見て、そのようなことを実装する方法を知ってください。

コードが多いことを警告する

簡単に言うと、Java 1.5 以降のラッパー クラスの仕組みを参照してください。

Integer a = Integer.valueOf( 2 );
Integer b = Integer.valueOf( 2 );

a == b 

は真です

new Integer( 2 ) == new Integer( 2 )  

は偽です。

内部的に参照を保持し、入力値が同じ場合はそれを返します。

ご存じのとおり、整数は読み取り専用です

その質問の元となった String クラスでも同様のことが起こります。

于 2008-12-11T18:04:21.293 に答える
0

平等は論理の基本であり (同一性の法則を参照)、それなしでできるプログラミングはほとんどありません。あなたが書いたクラスのインスタンスを比較することに関しては、それはあなた次第です。コレクションでそれらを見つけたり、マップでキーとして使用したりできるようにする必要がある場合は、等価チェックが必要になります。

equalsJava でいくつかの重要なライブラリを作成したことがある場合は、平等を正しく実現するのが難しいことを知っているでしょうhashCode。平等は最終的にクラス階層と密接に結びついてしまい、脆弱なコードになります。さらに、これらのメソッドは Object 型のパラメーターのみを受け取るため、型チェックは提供されません。

等値チェック (およびハッシュ) でエラーが発生しにくく、型安全性を高める方法があります。Functional Javaライブラリーには、等価性が単一のクラスに分離されている場所Equal<A>(および対応する) があります。Hash<A>これにはEqual、既存のインスタンスからクラスのインスタンスを作成するメソッドと、 and の代わりに and を使用する Collections、Iterables 、HashMap 、およびHashSetラッパーあります。Equal<A>Hash<A>equalshashCode

このアプローチの優れている点は、必要なときに equals と hash メソッドを記述することを決して忘れないことです。型システムは覚えるのに役立ちます。

于 2008-12-13T08:04:06.737 に答える
0

ほとんどの場合、 equals() をオーバーライドする主な理由は、特定のコレクション内の重複をチェックすることです。たとえば、作成したオブジェクトを Set に格納する場合は、オブジェクト内で equals() と hashCode() をオーバーライドする必要があります。カスタム オブジェクトを Map のキーとして使用する場合も同様です。

equals() と hashCode() をオーバーライドせずにカスタム オブジェクトをセットまたはマップに追加するという実際の間違いを多くの人が犯しているのを目にしたので、これは非常に重要です。これが特に危険な理由は、コンパイラが文句を言わず、重複を許可しない Collection 内に同じデータを含むが異なる参照を持つ複数のオブジェクトになってしまう可能性があるためです。

たとえば、単一の String 属性「name」を持つ NameBean という単純な Bean がある場合、それぞれ同じ「name」属性値 (「Alice」など) を持つ NameBean の 2 つのインスタンス (name1 と name2 など) を作成できます。次に、name1 と name2 の両方を Set に追加すると、セットは意図したサイズ 1 ではなくサイズ 2 になります。同様に、名前 Bean を他のオブジェクトにマップするために Map などのマップがあり、最初に name1 を文字列 "first" にマップし、後で name2 を文字列 "second" にマップした場合、両方のキーと値のペアがあります。マップ内 (例: name1->"first"、name2->"second")。そのため、マップ ルックアップを実行すると、渡された正確な参照にマップされた値が返されます。これは、名前 1、名前 2、または名前 " を持つ別の参照です。

これを実行した結果の前に具体的な例を示します。

出力:

Adding duplicates to a map (bad):
Result of map.get(bean1):first
Result of map.get(bean2):second
Result of map.get(new NameBean("Alice"): null

Adding duplicates to a map (good):
Result of map.get(bean1):second
Result of map.get(bean2):second
Result of map.get(new ImprovedNameBean("Alice"): second

コード:

// This bean cannot safely be used as a key in a Map
public class NameBean {
    private String name;
    public NameBean() {
    }
    public NameBean(String name) {
        this.name = name;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    @Override
    public String toString() {
        return name;
    }
}

// This bean can safely be used as a key in a Map
public class ImprovedNameBean extends NameBean {
    public ImprovedNameBean(String name) {
        super(name);
    }
    @Override
    public boolean equals(Object obj) {
        if (this == obj) {
            return true;
        }
        if(obj == null || getClass() != obj.getClass()) {
            return false;
        }
        return this.getName().equals(((ImprovedNameBean)obj).getName());
    }
    @Override
    public int hashCode() {
        return getName().hashCode();
    }
}

public class MapDuplicateTest {
    public static void main(String[] args) {
        MapDuplicateTest test = new MapDuplicateTest();
        System.out.println("Adding duplicates to a map (bad):");
        test.withDuplicates();
        System.out.println("\nAdding duplicates to a map (good):");
        test.withoutDuplicates();
    }
    public void withDuplicates() {
        NameBean bean1 = new NameBean("Alice");
        NameBean bean2 = new NameBean("Alice");

        java.util.Map<NameBean, String> map
                = new java.util.HashMap<NameBean, String>();
        map.put(bean1, "first");
        map.put(bean2, "second");
        System.out.println("Result of map.get(bean1):"+map.get(bean1));
        System.out.println("Result of map.get(bean2):"+map.get(bean2));
        System.out.println("Result of map.get(new NameBean(\"Alice\"): "
                + map.get(new NameBean("Alice")));
    }
    public void withoutDuplicates() {
        ImprovedNameBean bean1 = new ImprovedNameBean("Alice");
        ImprovedNameBean bean2 = new ImprovedNameBean("Alice");

        java.util.Map<ImprovedNameBean, String> map
                = new java.util.HashMap<ImprovedNameBean, String>();
        map.put(bean1, "first");
        map.put(bean2, "second");
        System.out.println("Result of map.get(bean1):"+map.get(bean1));
        System.out.println("Result of map.get(bean2):"+map.get(bean2));
        System.out.println("Result of map.get(new ImprovedNameBean(\"Alice\"): "
                + map.get(new ImprovedNameBean("Alice")));
    }
}
于 2008-12-11T22:13:37.063 に答える