Effective-Java-40坚持使用Override注解

Java 类库中包含了几种注解类型。一般来说,其中最重要的是 @Override 注解。该注解仅能用于方法声明,表示被注解的方法声明覆盖了超类中的一个方法声明。坚持使用该注解,可以防止一大类的非法错误。[1]请看代码段

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
/**
* Can you spot the bug?
* result: 260
*/
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 first == b.first && second == b.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 ,因为存在重复添加相同字母组合的情况。[2]

显然 Bigram 类的创建者原本想覆盖 equals 方法(见第10条),同时还记得覆盖 hashCode。实际上 equals 没有被重写,而是被重载了。[3]重写 Object.equals 必须定义一个参数为 Object 类型的 equals 方法,但Bigram 类中定义的是 Bigram 类型,因此 Bigram 类从Object 类继承了equals ,该 equals 比较对象的同一性 (identity),就像 == 操作符一样。[4]所以对于每一个 bigram 的重复添加,都被看做是不同的,这就是结果为 260 的原因。[5]

幸运的是,编译器可以可以帮助你发现这个错误,但是需要告知编译器想要重写 Object.equals才行。用 @Override 标注Bigram.equals,如下[6]

1
2
3
4
5
6
7
8
/**
* DON'T DO THIS!
* Error: Method does not override method from its superclass
*/
@Override
public boolean equals(Bigram b) {
return first == b.first && second == b.second;
}

如果插入这个注解,会发现错误信息。将其改正为:

1
2
3
4
5
6
7
@Override
public boolean equals(Object o) {
if (!(o instanceof Bigram))
return false;
Bigram b = (Bigram) o;
return first == b.first && second == b.second;
}

因此,应在想要重写超类声明的每个方法中使用 Override 注解[7]


  1. Oracle Java SE 21 API, java.lang.Override, defines @Override as a method-declaration annotation and specifies the compiler error condition when the annotated method does not override, implement, or match an Object public method. ↩︎

  2. Joshua Bloch, Effective Java, 3rd Edition, Item 40, uses the Bigram example to motivate consistent use of @Override. ↩︎

  3. Java Language Specification SE 21, §8.4.2 Method Signature and §8.4.8.1 Overriding, distinguish methods by name and parameter types and define when an instance method overrides another. ↩︎

  4. Oracle Java SE 21 API, Object.equals(Object), documents that the default implementation uses reference equality, equivalent to == for non-null references. ↩︎

  5. Oracle Java SE 21 API, HashSet, documents a hash-table-backed Set; together with Object.equals(Object) and Object.hashCode(), this supports why distinct Bigram instances remain distinct when equals(Object) is not actually overridden. ↩︎

  6. Oracle Java SE 21 API, java.lang.Override, requires compilers to report an error when an annotated method does not meet one of the permitted override/implementation conditions. ↩︎

  7. Effective Java Item 40 gives the practice rule to use @Override consistently; Error Prone, MissingOverride, shows the same rule encoded as a static-analysis check. ↩︎