[英]Understanding FindBugs warning about Class doesn't override equals in superclass
根據Josh Bloch在Java中的說法:
除非您願意放棄面向對象抽象的好處,否則無法擴展可實例化的類並在保留equals合同的同時添加值組件。
所以這就是我的情況,我有一個類Foo
,它已經覆蓋了Intellij Idea實現的equals()
和hashcode()
。 現在我有另一個類FooChild
,它擴展了Foo
並為'Foo`增加了幾個字段。 現在,FindBugs抱怨FooChild:
類不會覆蓋超類中的equals此類擴展一個定義equals方法並添加字段的類,但不定義equals方法本身。 因此,此類的實例上的相等性將忽略子類的標識和添加的字段。 確保這是預期的,並且您不需要覆蓋equals方法。 即使您不需要重寫equals方法,也可以考慮覆蓋它,以便記錄子類的equals方法只返回調用super.equals(o)的結果這一事實。
我的問題是“ 這個類的實例上的相等是什么意思會忽略子類的身份 ?我理解忽略添加字段的部分,因為還沒有為它們編寫equals()
方法。
“類的實例上的相等性將忽略子類的身份是什么意思?”
如果類Foo
有一個equals()
方法,那么FooChild
繼承它,這意味着如果使用equals()
比較FooChild
兩個實例,將調用Foo.equals()
方法。
如果FooChild
有任何數據成員,那么FooChild
兩個實例可能在其Foo
部分中具有相同的成員值,但是直接在類中定義的成員的值不同。 但是Foo.equals()
方法只會查看Foo
定義的成員,因此將兩個這樣的對象發音為equals()
,即使它們的FooChild
部分不同。
這就是你需要在FooChild
重寫equals()
的FooChild
。
現在,如果你使用equals()
比較一個Foo
和一個FooChild
,當兩個類都有自己的equals()
版本時會發生什么? 它取決於你調用equals()
對象,它取決於你如何實現兩個equals()
方法。 坦白說,這太亂了! 這就是第一句話的意思,即Josh Bloch的意思。 定義這兩個equals()
方法是不可能的,所以它們總能做正確的事情。 因此,最好避免一個值類(即,其標識與其成員變量的值綁定的類)擴展另一個值類的情況。
首先,在計算equals
或hashCode
方法時,不應該包括來自超類的任何狀態。 該狀態超出了您正在處理的類的范圍,您的類應該依賴於超類而不是重寫其功能。
當使用IntelliJ生成equals
和hashCode
時,如果要包含來自父類的任何計算,只需對添加對super.equals(o)
或super.hashCode()
的調用的方法進行一些小修改。
實現這些方法的另一種方法是使用Apache Commons Lang庫中的EqualsBuilder
和HashCodeBuilder
,這些構建器提供語義來添加超級調用。
編輯:
回答“這個類的實例上的相等性是什么意思會忽略子類的身份?” :正如您已經指出的那樣,這意味着如果您有兩個類,一個擴展另一個類,並且子類不會覆蓋equals
或hashCode
,那么,在檢查兩個實例是否相等(或添加到散列結構)時兩者都表現得好像是來自同一個班級。 這是一個例子:
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;
}
}
正如我已經說過的,其他方式是使用Apache Commons Lang庫中的構建器。
處理這類問題的通常方法是采用組合而不是繼承。 這是必要的,因為正如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()方法通常是一個好主意,這樣它們就不會被破壞的實現再次覆蓋。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.