3

JavaのJosh Blochによると:

オブジェクト指向の抽象化の利点を放棄する意思がない限り、インスタンス化可能なクラスを拡張し、equals 契約を維持しながら値コンポーネントを追加する方法はありません。

これが私のケースで、Fooオーバーライドされ、Intellij Idea によって実装されたクラスがequals()ありhashcode()ました。FooChildこれで、「Foo」を拡張Fooしてさらにいくつかのフィールドを追加する別のクラスができました。現在、FindBugs は FooChild について不平を言っています。

クラスがスーパークラスで equals をオーバーライドしない このクラスは、equals メソッドを定義してフィールドを追加するクラスを拡張しますが、equals メソッド自体は定義しません。したがって、このクラスのインスタンスが等しい場合、サブクラスの ID と追加されたフィールドは無視されます。これが意図したものであり、equals メソッドをオーバーライドする必要がないことを確認してください。equals メソッドをオーバーライドする必要がない場合でも、オーバーライドして、サブクラスの equals メソッドが super.equals(o) を呼び出した結果を返すだけであるという事実を文書化することを検討してください。

私の質問は、「このクラスのインスタンスの等価性は、サブクラスのアイデンティティを無視するequals()ことを意味しますか?メソッドがまだ作成されていないため、追加されたフィールドを無視することについての部分を理解しています.

4

3 に答える 3

5

「このクラスのインスタンスの等価性はサブクラスのアイデンティティを無視する」とはどういう意味ですか?

classFooequals()メソッドがある場合、それFooChildを継承します。つまり、FooChildusingの 2 つのインスタンスを比較するequals()と、Foo.equals()メソッドが呼び出されます。

にデータ メンバーがある場合、 の 2 つのインスタンスがそれぞれの部分のメンバーに対して同じ値を持つFooChild可能性がありますが、クラスで直接定義されたメンバーに対して異なる値を持つ可能性があります。しかし、このメソッドは で定義されたメンバーのみを参照するため、そのような 2 つのオブジェクトは と発音されますが、それらの部分は異なります。FooChildFooFoo.equals()Fooequals()FooChild

equals()これが、でオーバーライドする必要がある理由ですFooChild

では、両方のクラスが独自のバージョンの を持っている場合、 aFooFooChildusingを比較するとどうなるでしょうか? どのオブジェクトを呼び出すか、および 2 つのメソッドをどのように実装するかによって異なります。はっきり言ってめちゃくちゃです!それが最初の引用、Josh Bloch からの引用の意味です。これら 2 つのメソッドを定義することは不可能であるため、常に正しいことを行います。したがって、1 つの値クラス (つまり、ID がそのメンバー変数の値に結び付けられているクラス) が別の値クラスを拡張する状況は避けるのが最善です。equals()equals()equals()equals()equals()

于 2012-07-27T11:50:09.403 に答える
3

equalsまず、またはhashCodeメソッドを計算するときに、スーパークラスからの状態を含めないでください。その状態は、作業しているクラスの範囲外であり、クラスは、その機能を書き換えるのではなく、スーパークラスに依存する必要があります。

equalsIntelliJを使用して生成するときhashCodeに、親クラスからの計算を含める場合は、super.equals(o)またはへの呼び出しを追加するメソッドに小さな変更を加えるだけsuper.hashCode()です。

これらのメソッドを実装する別の方法は、EqualsBuilderApache Commons Langライブラリーを使用することです。これらのビルダーは、スーパー呼び出しを追加するためのセマンティクスを提供します。HashCodeBuilder

編集:

「このクラスのインスタンスの平等とは、サブクラスのIDを無視するという意味ですか?」と答えます。:すでに指摘したように、2つのクラスがあり、一方が他方を拡張し、サブクラスがどちらもオーバーライドしない場合、equalsまたはhashCode、2つのインスタンスが等しいかどうかをチェックする(またはハッシュ構造に追加する)と、両方が同じクラスのものであるかのように動作します。次に例を示します。

class A {
   private int intField = 2;

   public A(int value) {
       intField = value;
   }

   public boolean equals(Object o) {
       if (null == o) return false;
       if (this == o) return true;
       if (!(o instanceof A)) return false;

       return intField == ((A) o).intField;
   }

   public int hashCode() {
       return 11 * intField;
   }
}

class B extends A {
   private boolean boolField = true;

   public B(int intValue, boolean boolValue) {
       super(intValue);
       boolField = boolValue;
   }

   // no equals or hashCode
}

したがって、これらのクラスでは、次のことに直面します。

A a = new A(12);
B b = new B(12, false);
b.equals(a);    // returns true

これを回避するには、IntelliJで生成した後、equalsまたはメソッド内にスーパーコールを追加するだけです。hashCode

class B extends A {
   private boolean boolField = true;

   public B(int intValue, boolean boolValue) {
       super(intValue);
       boolField = boolValue;
   }

   public boolean equals(Object o) {
       if (null == o) return false;
       if (this == o) return true;
       if (!(o instanceof B)) return false;
       if (!super.equals(o)) return false;

       return boolField == ((B) o).boolField;
   }

   public int hashCode() {
       int hash = super.hashCode();
       hash += 11 * Boolean.valueOf(boolField).hashCode();
       return hash;
   }

}

もう1つの方法は、すでに述べたように、ApacheCommonsLangライブラリのビルダーを使用することです。

于 2012-07-27T10:32:10.367 に答える
1

この種の問題に対処するための通常のアプローチは、継承ではなく構成に頼ることです。Josh Block が彼の優れた著書「Effective Java」で詳述しているように、equals(Object) の一般的な契約を破らずにプロパティを考慮する equals(Object) の実装を実際にオーバーライドすることはできないため、これが必要です。

例:

class Foo {
    boolean property;

    public boolean equals(Object that) {
        return this == that
            || (that instanceof Foo)
                && this.property == ((Foo) that).property;
    }

    public int hashCode() { ... } // needs to be consistent with equals(Object).
}

class Bar extends Foo {
    boolean anotherProperty;

    // This is broken - DO NOT USE IT!
    public boolean equals(Object that) {
        return super.equals(that)
            && (that instanceof Bar)
            && this.anotherProperty == ((Bar) that).anotherProperty
    }

    public int hashCode() { ... } // needs to be consistent with equals(Object).
}

Bar.equals(Object) の実装が壊れているのはなぜですか? 単純に対称性を破るからです!これが証明です:

Foo foo = new Foo();
Bar bar = new Bar();
assert foo.equals(bar); // passes because bar is a Foo and property is zero
assert bar.equals(foo); // throws up because foo isn't an instance of Bar!

この種の問題は継承では解決できません。コンポジションを使用する必要があります。

結果として、equals(Object) および hashCode() メソッドをオーバーライドするときに final を宣言して、壊れた実装で再びオーバーライドされないようにすることをお勧めします。

于 2012-07-27T12:04:46.277 に答える