第36条:坚持使用Override
注解
随着 Java 1.5 发行版本中增加注解,类库中也增加了集中注解类型[JLS,9.6]。对于传统的程序员而言,这里面最重要的就是Override
注解了。这个注解只能用在方法声明中,它表示被注解的方法声明覆盖了超类型中的一个声明。如果坚持使用这个注解,可以防止一大类的非法错误。考虑下面的程序,这里的类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 void static main(String[] args) {
Set<Bigram> s = new HashSet<Bigram>();
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个双字母添加到集合中,每个双子母都由两个相同的小写字母组成。随后打印出集合的大小。你可能以为程序打印出的大小为 26,因为集合不能包含重复。如果你试着运行程序,会发现打印的不是 26 而是 260。哪里出错了呢?
很显然,Bigram
类的创建者原本想要覆盖equals
方法(见第8条),同时还记得覆盖了hashCode
。遗憾的是,不幸的程序员没能覆盖equals
,而是将它重载了(见第41条)。为了覆盖Object.equals
,必须定义一个参数为Object
类型的equals
方法,但是Bigram
的equals
方法的参数并不是Object
类型,因此Bigram
从Object
继承了equals
方法。这个equals
方法测试对象的同一性,就行==
操作符一样。每个bigram
的10个备份中,每一个都与其余的9个不同,因此Object.equals
认为它们不相等,这正解释了程序为什么会打印出 260 的原因。
幸运的是,编译器可以帮助你发现这个错误,但是只有当你告知编译器你想要覆盖Object.equals
时才行。为了做到这一点,要用@Override
标注Bigram.equals
,如下所示:
@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
@Override public boolean equals(Bigram b) {
^
你会立即意识到哪里错了,拍拍自己的头,恍然大悟,马上用正确的来取代出错的equals
实现(见第8条):
@Override public boolean equals(Bigram b) {
if (!(o instanceof Bigram))
return false;
Bigram b = (Bigram) o;
return b.first == first && b.second == second;
}
因此,应该在你想要覆盖超类声明的每个方法声明中使用Override
注解。这一规则有个小小的例外。如果你在编写一个没有标注为抽象的类,并且确信它覆盖了抽象的方法,在这种情况下,就不必将Override
注解放在该方法上了。在没有声明为抽象的类中,如果没有覆盖抽象的超类方法,编译器就会发出一条错误消息。但是,你可能希望关注类中所有覆盖超类方法的方法,在这种情况下,也可以放心地标注这些方法。
现代的IDE提供了坚持使用Override
注解的另一种理由。这种IDE具有自动检查功能,称作代码检验(code inspection)。如果启用相应的代码检验功能,当有一个方法没有Override
注解,却覆盖了超类方法时,IDE就会产生一条警告。如果坚持使用Override
注解,这些警告就会提醒你警惕无意识的覆盖。这些警告补充了编译器的错误消息,提醒你无意识的覆盖失败。IDE和编译器,可以确保你覆盖任何你想要覆盖的方法,无一遗漏。
如果你使用的是 Java 1.6 或者更新的发行版本,Override
注解在查找Bug方面还提供了更多的帮助。在 Java 1.6 发行版本中,在覆盖接口以及类的方法声明中使用Override
注解变成是合法的了。在被声明为去实现某接口的具体类中,不必标注出你想要这些方法来覆盖接口方法,因为如果你的类没有实现每一个接口方法,编译器就会产生一条错误消息。当然,你可以选择只包括这些注解,来标明它们是接口方法,但是这并非绝对必要。
但是在抽象类或者接口中,还是值得标注所有你想要的方法,来覆盖超类或者超接口方法,无论是具体的还是抽象的。例如,Set
接口没有给Collection
接口添加新方法,因此它应该在它的所有方法声明中包括Override
注解,以确保它不会意外地给Collection
接口添加任何新方法。
总而言之,如果在你想要的每个方法声明中使用Override
注解来覆盖超类声明,编译器就可以替你防止大量的错误,但有一个例外。在具体的类中,不必标注你确信覆盖了抽象方法声明的方法(虽然这么做也没有什么坏处)。