Tips
《Effective Java, Third Edition》一书英文版已经出版,这本书的第二版想必不少人都读过,号称Java四大名著之一,不过第二版2009年出版,到如今已经将近8年的时间,但随着Java 6,7,8,甚至9的发布,Java语言发生了深入的变化。
在这里第一时间翻译成中文版。供你们学习分享之用。java
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,由于集合不能包含重复项。 若是你尝试运行程序,你会发现它打印的不是26,而是260。它有什么问题?ide
显然,Bigram
类的做者打算重写equals
方法(条目 10),甚至记得重写hashCode
(条目 11)。 不幸的是,咱们倒霉的程序员没有重写equals
,而是重载它(条目 52)。 要重写Object.equals,必须定义一个equals方法,其参数的类型为Object,但Bigram
的equals方法的参数不是Object类型的,所以Bigram
继承Object的equals方法,这个equals方法测试对象的引用是不是同一个,就像==运算符同样。 每一个祖母组合的10个副本中的每个都与其余9个副本不一样,因此它们被Object.equals视为不相等,这就解释了程序打印260的缘由。学习
幸运的是,编译器能够帮助你找到这个错误,但只有当你经过告诉它你打算重写Object.equals来帮助你。 要作到这一点,用@Override
注解Bigram.equals方法,以下所示:测试
@Override public boolean equals(Bigram b) { return b.first == first && b.second == second; }
若是插入此注解并尝试从新编译该程序,编译器将生成以下错误消息:this
Bigram.java:10: method does not override or implement a method from a supertype @Override public boolean equals(Bigram b) { ^
你会马上意识到你作错了什么,在额头上狠狠地打了一下,用一个正确的(条目 10)来替换出错的equals实现:翻译
@Override public boolean equals(Object o) { if (!(o instanceof Bigram)) return false; Bigram b = (Bigram) o; return b.first == first && b.second == second; }
所以,应该在你认为要重写父类声明的每一个方法声明上使用Override
注解。 这条规则有一个小例外。 若是正在编写一个没有标记为抽象的类,而且确信它重写了其父类中的抽象方法,则无需将Override
注解放在该方法上。 在没有声明为抽象的类中,若是没法重写抽象父类方法,编译器将发出错误消息。 可是,你可能但愿关注类中全部重写父类方法的方法,在这种状况下,也应该随时注解这些方法。 大多数IDE能够设置为在选择重写方法时自动插入Override
注解。code
大多数IDE提供了是种使用Override
注解的另外一个理由。 若是启用适当的检查功能,若是有一个方法没有Override
注解可是重写父类方法,则IDE将生成一个警告。 若是始终使用Override
注解,这些警告将提醒你无心识的重写。 它们补充了编译器的错误消息,这些消息会提醒你无心识重写失败。 IDE和编译器,能够确保你在任何你想要的地方和其余地方重写方法,万无一失。对象
Override
注解可用于重写来自接口和类的方法声明。 随着default默认方法的出现,在接口方法的具体实现上使用Override
以确保签名是正确的是一个好习惯。 若是知道某个接口没有默认方法,能够选择忽略接口方法的具体实现上的Override
注解以减小混乱。blog
然而,在一个抽象类或接口中,值得标记的是你认为重写父类或父接口方法的全部方法,不管是具体的仍是抽象的。 例如,Set接口不会向Collection接口添加新方法,所以它应该在其全部方法声明中包含Override
注解以确保它不会意外地向Collection接口添加任何新方法。
总之,若是在每一个方法声明中使用Override
注解,而且认为要重写父类声明,那么编译器能够保护免受不少错误的影响,但有一个例外。 在具体的类中,不须要注解标记你确信能够重写抽象方法声明的方法(尽管这样作也没有坏处)。