14.1 基本注解

14.1 基本注解

注解必须使用工具来处理,工具负责提取注解里包含的元数据,工具还会根据这些元数据增加额外的功能。在系统学习新的注解语法之前,先看一下Java提供的5个基本注解的用法:使用注解时要在其前面增加@符号,并把该注解当成一个修饰符使用,用于修饰它支持的程序元素。

5个基本注解

5个基本的注解如下:

  • @Override
  • @Deprecated
  • @SuppressWarnings
  • @SafeVarargs
  • @FunctionalInterface

上面5个基本注解中的 @SafeVarargsJava7新增的、@FunctionalInterfaceJava8新增的。

这5个基本定义在java.lang包中

这5个基本的注解都定义在java.lang包下,读者可以通过查阅它们的API文档来了解关于它们的更多细节。

14.1.1 限定重写父类方法:@Override

@Override就是用来指定方法覆载的,它可以强制一个子类必须覆盖父类的方法。

程序

如下程序中使用@Override指定子类Appleinfo()方法必须重写父类方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class Fruit {
public void info() {
System.out.println("水果的info方法...");
}
}

class Apple extends Fruit {
// 使用@Override指定下面方法必须重写父类方法
@Override
// public void inf0() {
public void info() {
System.out.println("苹果重写水果的info方法...");
}
}

@Override可避免方法重写时 写错方法签名

编译上面程序,可能丝毫看不出程序中的@Override有何作用,因为@Override的作用是告诉编译器检査这个方法,保证父类要包含一个被该方法重写的方法,否则就会编译出错。@Override主要是帮助程序员避免一些低级错误,例如把上面Apple类中的info()方法不小心写成了inf0,这样的“低级错误”可能会成为后期排错时的巨大障碍
如果把Apple类中的info()方法误写成inf0(),编译程序时将出现如下错误提示

1
2
3
4
5
G:\Desktop\随书源码\疯狂Java讲义(第4版)光盘\codes\14\14.1>javac -encoding utf-8 Fruit.java
Fruit.java:9: 错误: 方法不会覆盖或实现超类型的方法
@Override
^
1 个错误

@Override只能修饰方法

@Override只能修饰方法,不能修饰其他程序元素。

14.1.2 Java9增强的@Deprecated

@Deprecated用于表示某个程序元素(类、方法等)已过时,当其他程序使用已过时的类、方法时,编译器将会给出警告。

Java9增加的属性

Java9@Deprecated注解增加了如下两个属性:

属性 描述
boolean forRemoval 指定该API在将来是否会被删除。
String since 指定该API从哪个版本被标记为过时。

程序

如下程序指定Apple类中的info方法已过时,其他程序中使用Apple类的info法时编译器将会给出警告

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Apple {
// 定义info方法已过时
// since属性指定从哪个版本开始,forRemoval指定该API将来会被删除
@Deprecated(since = "9", forRemoval = true)
public void info() {
System.out.println("Apple的info方法");
}
}

public class DeprecatedTest {
public static void main(String[] args) {
// 下面使用info方法时将会被编译器警告
new Apple().info();
}
}

上面程序中使用了Appleinfo()方法,而Apple类中定义info()方法时使用了@Deprecated修饰,表明该方法已过时,所以将会引起编译器警告。

@Deprecated@deprecated文档标记的区别

@Deprecated的作用与文档注释中的@deprecated标记的作用基本相同,但它们的用法不同:
@DeprecatedJDK5才支持的注解,无须放在文档注释语法(/**......*/部分)中,而是直接用于修饰程序中的程序单元,如方法、类、接口等

14.1.3 抑制编译器警告:@SuppressWarnings

@SuppressWarnings指示被该注解修饰的程序元素(以及该程序元素中的所有子元素)取消显示指定的编译器警告@SuppressWarnings会一直作用于该程序元素的所有子元素,例如,使用@SuppressWarnings修饰某个类取消显示某个编译器警告,同时又修饰该类里的某个方法取消显示另一个编译器警告,那么该方法将会同时取消显示这两个编译器警告。

程序

1
2
3
4
5
6
7
8
import java.util.*;

@SuppressWarnings(value = "unchecked")
public class SuppressWarningsTest {
public static void main(String[] args) {
List<String> myList = new ArrayList(); // ①
}
}

程序中的粗体字代码使用@SuppressWarnings来关闭SuppressWarningsTest类里的所有编译器警告,编译上面程序时将不会看到任何编译器警告。如果删除程序中的@SuppressWarnings(value = "unchecked")注解,将会在程序的①处看到编译器警告:

1
2
3
G:\Desktop\随书源码\疯狂Java讲义(第4版)光盘\codes\14\14.1>javac -encoding utf-8 SuppressWarningsTest.java  
注: SuppressWarningsTest.java使用了未经检查或不安全的操作。
注: 有关详细信息, 请使用 -Xlint:unchecked 重新编译。

正如从程序中粗体字代码所看到的,当使用@SuppressWarnings注解来关闭编译器警告时,一定要在括号里使用name=Value的形式为该注解的成员变量设置值。关于如何为注解添加成员变量请看下节介绍。

14.1.4 “堆污染”警告与Java9增强的@SafeVarargs

前面介绍泛型擦除时,介绍了如下代码可能导致运行时异常。

堆污染

Java把引发这种错误的原因称为“堆污染”(HeapPollution),当把一个不带泛型的对象赋给一个带泛型的变量时,往往就会发生这种“堆污染”,如上①号粗体字代码所示。
对于形参个数可变的方法,该形参的类型又是泛型,这将更容易导致“堆污染”

程序

例如如下工具类。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import java.util.*;

public class ErrorUtils {
// @SafeVarargs
public static void faultyMethod(List<String>... listStrArray) {
// Java语言不允许创建泛型数组,因此listArray只能被当成List[]处理
// 此时相当于把List<String>赋给了List,已经发生了“擦除”
List[] listArray = listStrArray;
List<Integer> myList = new ArrayList<Integer>();
myList.add(new Random().nextInt(100));
// 把listArray的第一个元素赋为myList
listArray[0] = myList;
String s = listStrArray[0].get(0);
}
}

上面程序中的代码:

1
List[] listArray = listStrArray;

已经发生了“堆污染”。由于该方法有个形参是List<String>类型,个数可变的形参相当于数组,但Java又不支持泛型数组,因此程序只能把List<String>当成List[]处理,这里就发生了“堆污染”。
Java6以及更早的版本中,Java编译器认为faultyMethod()方法完全没有问题,既不会提示错误也没有提示警告。
等到使用该方法时,例如如下程序。

1
2
3
4
5
6
7
import java.util.*;

public class ErrorUtilsTest {
public static void main(String[] args) {
ErrorUtils.faultyMethod(Arrays.asList("Hello!"), Arrays.asList("World!"));//①
}
}

编译该程序将会在①号代码处引发一个unchecked警告。这ˆunchecked警告岀现得比较“突兀”定义faultyMethod()方法时没有任何警告,调用该方法时却引发了一个“警告”
上面程序故意利用了“堆污染”,因此程序运行时也会在①号代码处引发ClassCastException异常。
Java7开始,Java编译器将会进行更严格的检查,Java编译器在编译ErrorUtils时就会发出一个如下所示的警告

1
2
3
G:\Desktop\随书源码\疯狂Java讲义(第4版)光盘\codes\14\14.1>javac -encoding utf-8 ErrorUtils.java
注: ErrorUtils.java使用了未经检查或不安全的操作。
注: 有关详细信息, 请使用 -Xlint:unchecked 重新编译。

由此可见,Java7会在定义该方法时就发出“堆污染”警告,这样保证开发者“更早”地注意到程序中可能存在的“漏洞”

抑制堆污染警告的三种方法

但在有些时候,开发者不希望看到这个警告,则可以使用如下三种方式来“抑制”这个警告。

  • 使用@SafeVarargs修饰引发该警告的方法或构造器。Java9增强了该注解,允许使用该注解修饰私有实例方法。
  • 使用@SuppressWarnings("unchecked")修饰。
  • 编译时使用-Xlint:unchecked选项。

很明显,第三种方式一般比较少用,通常可以选择第一种或第二种方式,尤其是使用@SafeVarargs修饰引发该警告的方法或构造器,它是Java7专门为抑制“堆污染”警告提供的。
如果程序使用@SafeVarargs修饰ErrorUtils类中的faultyMethod()方法,则编译上面两个程序时都不会发出任何警告

14.1.5 Java8的函数式接口与@FunctionalInterface

前面已经提到,Java8规定:只有一个抽象方法的接口就是函数式接口(注意:函数式接口可以包含多个默认方法或多个static方法)
@FunctionalInterface就是用来指定某个接口必须是函数式接口
函数式接口就是为Java8Lambda表达式准备的,Java8允许使用Lambda表达式创建函数式接口的实例,因此Java8专门增加了@ .Functionalnterface

程序

例如,如下程序使用@FunctionalInterface修饰了函数式接口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@FunctionalInterface
public interface FunInterface {
static void foo() {
System.out.println("foo类方法");
}

default void bar() {
System.out.println("bar默认方法");
}

void test(); // 只定义一个抽象方法
// 再定义抽象方法
// void abc();
}

编译上面程序,可能丝毫看不出程序中的@FunctionalInterface有何作用,因为@FunctionalInterface只是告诉编译器检査这个接口,保证该接口只能包含一个抽象方法,否则就会编译出错。@FunctionalInterface主要是帮助程序员避免一些低级错误,例如,在上面的FunInterface接口中再增加一个抽象方法abc(),编译程序时将出现如下错误提示:

@FunctionalInterface只能修饰接口,不能修饰其他程序元素。

1
2
3
4
5
6
7
G:\Desktop\随书源码\疯狂Java讲义(第4版)光盘\codes\14\14.1>javac -encoding utf-8 FunInterface.java
FunInterface.java:1: 错误: 意外的 @FunctionalInterface 注释
@FunctionalInterface
^
FunInterface 不是函数接口
在 接口 FunInterface 中找到多个非覆盖抽象方法
1 个错误