TIP 36 坚持使用Override注解
用IDE写代码的同学可能见怪不怪了,无论是实现接口,还是实现父类的abstract方法,IDE会自动在这些方法前加上这个注解。
不过,我们还是来看看如果不用这个注解,会有什么情况发生:
/**
* 一个表示双字母组的类。
*/
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());
}
}
main方法中有个双重循环,一共执行了260次add方法,但Set的特性就是不能包含重复,所以,s的大小是26,对不对?
当然不对,你运行一下就可以看到结果:260。
很显然,这个类想要覆盖equals方法,同时还记得覆盖了hashCode。不幸的是,equals没有被覆盖,而是被重载了。Object.equals的参数是Object类型,而Bigram.equals的参数是Bigram类型,不是Object类型,所以Bigram从Object继承了equals方法,而没有覆盖它。
而Object.equals方法测试对象的同一性,就像操作符 ==
一样。所以如果创建了10个Bigram实例,即使每个实例的first与second都完全相等,它们仍然是不同的对象,Object.equals方法也会返回false。这就是程序为什么会打印260的原因。
幸运的是,如果使用@Override注解,编译器就会通知一个编译器错误:
Error:(17, 5) java: 方法不会覆盖或实现超类型的方法
然后你就会明白,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。
原书还写了一大段话,中文版本的翻译看着实在绕口。这里稍微整理一下:
如果一个类A不是抽象类(abstract class),而你确信A覆盖了抽象的方法,在这种情况下,就没必要加上这个注解。因为如果A遗漏了某个抽象方法的覆盖,编译器会产生一条编译期错误。而且现在的IDE都会自动给A添加一个空的覆盖实现,而且自动加上这个注解。比如,豆爷用的Idea在错误的那行按下Alt+Enter,IDE会自动帮你添加这些方法体和注解。
如果懒的看,就请记住,任何覆盖父类或实现接口的方法,加上@Override,准没错。
TIP 37 用标记接口定义类型
标记接口 : 没有包含方法声明的接口,它用于标明一个类实现了具有某种属性的接口。比如Serializable接口,通过实现这个接口,类表明它的实例可以被写到ObjectOutputStream,即被序列化。
相比标记注解,有两个有点:
- 标记接口定义的类型是由被标记类的实例实现的,标记注解则没有定义这样的类型。这个类型允许你在编译时捕捉在使用标记注解的情况下要到运行时才能捕捉的错误。亦即更健壮,更早发现问题。
- 它们可以被更加精确地进行锁定。如果注解类型使用
@Target (ElementType.TYPE)
声明,它就可以被应用到任何类和接口。而假设有一个标记只适用于特殊接口的实现,将它定义为一个标记接口,就可以用它将唯一的接口扩展成它使用的接口。
而标记注解的优点在于,
- 它可以通过默认的方式添加一个或多个注解类型元素,给已被使用的注解类型添加更多的信息。随着时间的退役,简单的标记注解类型可以演变成更加丰富的注解类型,而这种演变对标记接口而言是不可能的。
- 它是更大的注解机制的一部分。因此,标记注解在那些支持注解作为编程元素之一的框架中同样具有一致性。
所以, 选择哪一种方式呢?
两种方式各有用处。
- 如果想定义一个任何新方法都不会与之关联的类型,标记接口就是最好的选择。
- 如果想要标记程序元素而非类和接口,考虑到未来可能要给标记添加更多的信息,或者标记要适合于已经广泛使用了注解类型的框架,那么标记注解就是合适的选择。
本条目的内容有些艰涩,因为平时用的很少,有些概念还不太清楚。此处立个Flag,有空回头一定把本TIP弄个仔细通透。
最后再说一句人话:如果想要定义类型,一定要使用接口。