ITEM 40: CONSISTENTLY USE THE OVERRIDE ANNOTATION
??Java庫包含幾種注解類型。對于一個程序員來說,最重要的是@Override。此注解只能用于方法聲明,它指示帶注解的方法聲明覆蓋超類型中的聲明。如果您始終使用這個注解,它將保護您免受大量惡意bug的攻擊??紤]下面這個例子,其中類 Bigram 表示一個 雙字母組,或有序的字母對:
// Can you spot the bug?
public class Bigram {
private final char first;
private final char second;
public Bigram(char first, char second) {
this.first = first;
this.second = second;
}
public boolean equals(Bigram b) {
return b.first == first && b.second == second;
}
public int hashCode() {
return 31 * first + second;
}
public static void main(String[] args) {
Set<Bigram> s = new HashSet<>();
for (int i = 0; i < 10; i++)
for (char ch = 'a'; ch <= 'z'; ch++)
s.add(new Bigram(ch, ch));
System.out.println(s.size());
}
}
??主程序反復向一個集合添加26個bigram,每個bigram由兩個相同的小寫字母組成。然后它打印集合的大小。如果你試著運行這個程序,你會發(fā)現(xiàn)它輸出的不是26而是260。有什么問題嗎?
??顯然,Bigram 類的作者打算重寫 equals 方法(iem 10),甚至還記得同時重寫 hashCode (item 11)。不幸的是,程序員沒有重寫equals,而是重載了它(item 52)。為了覆蓋 Object 的 equals 方法,我們必須定義一個參數(shù)是Object類型的equals方法,但是 Bigram 的 equals 方法的參數(shù)不是 Object 類型的,所以 Bigram 繼承了 Object 的 equals 方法。這個 equals 方法測試對象標識,就像 == 操作符一樣。每一個雙字母的十份副本都與其他九份不同,因此它們被認為是不相等的。等于,這就解釋了為什么程序輸出260。
??幸運的是,編譯器可以幫助您找到這個錯誤,但前提是您要告訴它您打算覆蓋 Object.equals。為此,請在 Bigram.equals 使用注解 @Override,如下所示:
@Override
public boolean equals(Bigram b) {
return b.first == first && b.second == second;
}
??如果插入此注解并嘗試重新編譯程序,編譯器將生成如下錯誤消息: "Bigram.java:10: method does not override or implement a method from a supertype"
??你會立刻意識到自己做錯了什么,給自己一記耳光,用正確的一記替換掉錯誤的 equals 實現(xiàn)(item 10):
@Override
public boolean equals(Object o) {
if (!(o instanceof Bigram))
return false;
Bigram b = (Bigram) o;
return b.first == first && b.second == second;
}
??因此,您應該在您認為要覆蓋超類聲明的每個方法聲明上使用 Override 注解。這條規(guī)則有一個小小的例外。如果您正在編寫一個沒有標記為abstract 的類,并且您認為它覆蓋了其超類中的抽象方法,那么您不需要在該方法上添加 Override 注解。在沒有聲明抽象的類中,如果未能覆蓋抽象超類方法,編譯器將發(fā)出錯誤消息。
??不過,您可能希望將注意力放在類中覆蓋超類方法的所有方法上,在這種情況下,您也可以隨意注解這些方法。大多數(shù) IDE 都可以設(shè)置為在選擇重寫方法時自動插入覆蓋注解。
??大多數(shù) IDE 都提供了一致使用覆蓋注解的另一個原因:如果啟用了適當?shù)臋z查,如果您的方法沒有覆蓋注解,但是覆蓋了超類方法,IDE 將生成一個警告。如果您始終如一地使用 Override 注解,這些警告將提醒您進行無意的覆蓋。它們補充了編譯器的錯誤消息,這些錯誤消息會警告您無意中覆蓋失敗。在 IDE 和編譯器之間,您可以確保您要覆蓋的方法精準的命中了,而不是在其他任何地方。
??覆蓋注解可以用于覆蓋來自接口和類的聲明的方法聲明。隨著缺省方法的出現(xiàn),在接口方法的具體實現(xiàn)上使用覆蓋來確保簽名是正確的,這是一個很好的實踐。如果知道接口沒有默認方法,可以選擇忽略接口方法的具體實現(xiàn)上的覆蓋注解,以減少混亂。
??但是,在抽象類或接口中,值得注解所有您認為覆蓋超類或超接口方法的方法,無論是具體的還是抽象的。例如,Set 接口沒有向 Collection 接口添加任何新方法,因此它應該包含覆蓋其所有方法聲明的注解,以確保不會意外地向集合接口添加任何新方法。
??總之,如果您在每個方法聲明上都使用 Override 注解,那么編譯器可以保護您不受很多錯誤的影響,因為您認為每個方法聲明都要覆蓋超類型聲明,只有一個例外:在具體類中,您不需要注解您認為可以覆蓋抽象方法聲明的方法(盡管這樣做沒有害處)。