java類庫包含了幾種注解類型。對(duì)于傳統(tǒng)的程序員而言,這里面最重要的就是@Override注解。這個(gè)注解只能用在方法聲明中,它表示被注解的方法聲明覆蓋了超類型中的一個(gè)方法聲明。如果堅(jiān)持使用這個(gè)注解,可以防止一大類的非法錯(cuò)誤。以下面的程序?yàn)槔?,這里的Bigram類表示一個(gè)雙字母組或者有序的字母對(duì):
// 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());
}
}
主程序反復(fù)的將26個(gè)雙字母組添加到集合中,每個(gè)雙字母組都由兩個(gè)相同的小寫字母組成。隨后它打印出集合的大小。你可能以為程序打印的大小為26,因?yàn)榧喜荒馨貜?fù)。如果你試著運(yùn)行程序,會(huì)發(fā)現(xiàn)它打印的不是26而是260。哪里出錯(cuò)了呢?
很顯然,Bigram類的創(chuàng)建者原本想要覆蓋equals方法(詳見第10條),同時(shí)還記得覆蓋了hashCode(詳見第11章)。遺憾的是,不幸的程序員沒能覆蓋equals方法,而是將它重載了(詳見第52條)。為了覆蓋Object.equals必須定義一個(gè)參數(shù)為Object類型的equlas方法,但是Bigram類的equals方法的參數(shù)并不是Object類型,因此Bigram類從Object繼承了equals方法。這個(gè)equals方法測(cè)試對(duì)象的同一性(identity),就像==操作符一樣。每個(gè)bigram的10個(gè)備份中,每一個(gè)都與其余的9個(gè)不同,因此Object.equals認(rèn)為它們不相等,這正是程序會(huì)打印出260的原因。
幸運(yùn)的是,編譯器可以幫助你發(fā)現(xiàn)這個(gè)錯(cuò)誤,但是只有當(dāng)你告知編譯器你想要覆蓋Object.equals時(shí)才發(fā)現(xiàn)。為了做到這一點(diǎn),要用@Override標(biāo)注Bigram.equals,如下所示:
@Override
public boolean equals(Bigram b) {
return b.first == first && b.second == second;
}
如果插入這個(gè)注解,并試著重新編譯程序,編譯器就會(huì)產(chǎn)生如下的錯(cuò)誤消息:
Bigram.java:10: method does not override or implement a method from a supertype
你會(huì)立即意識(shí)到哪里錯(cuò)了,拍拍自己的頭,恍然大悟,馬上用正確的來取代出錯(cuò)的equals實(shí)現(xiàn)(詳見第10條):
@Override
public boolean equals(Object o) {
if (!(o instanceof Bigram))
return false;
Bigram b = (Bigram) o;
return b.first == first && b.second == second;
}
因此,應(yīng)該在你想要覆蓋超類聲明的每個(gè)方法聲明中使用@Override注解。這一規(guī)則有個(gè)小小的例外。如果你在編寫一個(gè)沒有標(biāo)注為抽象的類,并且確信它覆蓋了超類的抽象方法,在這種情況下,就不必將Override注解放在該方法上了。在沒有聲明為抽象的類中,如果沒有覆蓋抽象的超類方法,編譯器就會(huì)發(fā)出一條錯(cuò)誤消息。但是,你可能希望關(guān)注類中所有覆蓋超類方法的方法,在這種情況下,也可以放心的標(biāo)注這些方法。大多數(shù)IDE可以設(shè)置為在需要覆蓋的一個(gè)方法時(shí)自動(dòng)插入Override注解。
大多數(shù)IDE都提供了使用Override注解的另一種理由。如果啟用相應(yīng)的代碼檢驗(yàn)功能,當(dāng)有一個(gè)方法沒有Override注解,卻覆蓋了超類方法時(shí),IDE就會(huì)產(chǎn)生一條警告。如果使用了Override注解,這些警告就會(huì)提醒你警惕無意識(shí)的覆蓋。這些警告補(bǔ)充了編譯器的錯(cuò)誤消息,后者會(huì)提醒你警惕無意識(shí)的覆蓋失敗。IDE和編譯器可以確保你無一遺漏的覆蓋任何你想覆蓋的方法。
Override注解可以用在方法聲明中,覆蓋來自接口以及類的聲明。由于缺省方法的出現(xiàn),在接口方法的具體是線上使用Override,可以確保簽名正確,這是一個(gè)很好的實(shí)踐。如果知道接口沒有缺省方法,可以選擇省略方法的具體實(shí)現(xiàn)上的Override注解,以減少混亂。
但是在抽象類或者接口中,還是值得標(biāo)注所有你想要的方法,來覆蓋超類或者超接口方法,無論它們是具體的還是抽象的。例如,Set接口沒有給Collection接口添加新方法,因此他應(yīng)該在它的所有方法聲明中包括Override注解,以確保它不會(huì)意外的給Collection接口添加任何新方法。
總而言之,如果在你想要的每個(gè)方法聲明中使用Override注解來覆蓋超類聲明,編譯器就可以替你防止大量的錯(cuò)誤,但有一個(gè)例外。在具體的類中,不必標(biāo)注你確信覆蓋了抽象方法聲明的方法(雖然這么做也沒什么壞處)。