摘要
JDK:1.8.0_202
# 一:基本Annotation
@Override
:指定方法覆盖,用来帮程序员避免一些低级错误,只能修饰方法;@Deprecated
:表示某个程序元素(类、方法等)已过时,当其他程序使用已过时类、方法时,编译器将会给出警告;@SuppressWarnings
:被该Annotation修饰的程序元素(以及该程序元素中的所有子元素)取消显示指定的编译器警告;@FunctionalInterface
:指定某个接口必须是函数式接口。
@Deprecated的作用与文档注释中的@deprecated标记的作用基本相同,但它们的用法不同,前者是JDK 5才支持的注解,无须放在文档注释语法(/**…*/部分)中,而是直接用于修饰程序中的程序单元,如方法、类、接口等。
示例一:
// 关闭整个类里的编译器警告
@SuppressWarnings(value = "unchecked")
public class Test {
// 重写 Object 类 toString 方法
@Override
public String toString() {
return "Test{}";
}
// 定义 info 方法已过时
@Deprecated
public void deprecated() {
System.out.println("方法已过时");
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
示例二:
/**
* Java 8规定:如果接口中只有一个抽象方法(可以包含多个默认方法或多个static方法),该接口就是函数式接口。
* @FunctionalInterface就是用来指定某个接口必须是函数式接口。
*/
@FunctionalInterface
public interface FunInterface {
static void foo() {
System.out.println("foo 类方法");
}
default void bar() {
System.out.println("bar 默认方法");
}
// 抽象方法
void test();
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 二:自定义 Annotation
定义新的Annotation类型使用 @interface
关键字。Annotation 的成员变量在 Annotation 定义中以无形参的方法形式来声明,其方法名和返回值定义了该成员变量的名字和类型。
// 指定该注解信息会保留到运行时 - 下面内容会讲解
@Retention(RetentionPolicy.RUNTIME)
// 作用在方法或成员变量上 - 下面内容会讲解
@Target({ElementType.METHOD, ElementType.FIELD})
public @interface MyTag {
// 定义成员变量
String name();
// 定义成员变量指定初始值
int age() default 18;
}
public class Test1 {
@MyTag(name = "xx")
public void info1() {
}
@MyTag(name = "xx", age = 22)
public void info2() {
}
}
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
# 三:元 Annotation
java.lang.annotation 包下提供了6个Meta Annotation(元Annotation),其中有5个元Annotation都用于修饰其他的Annotation定义。
# 3.1 @Retention
@Retention 只能用于修饰 Annotation 定义,用于指定被修饰的 Annotation 可以保留多长时间,@Retention包含一个RetentionPolicy类型的value成员变量,所以使用@Retention时必须为该value成员变量指定值。
RetentionPolicy.CLASS
:编译器将把Annotation记录在class文件中。当运行Java程序时,JVM不可获取Annotation信息。这是默认值;RetentionPolicy.RUNTIME
:编译器将把Annotation记录在class文件中。当运行Java程序时,JVM也可获取Annotation信息,程序可以通过反射获取该Annotation信息;RetentionPolicy.SOURCE
:Annotation只保留在源代码中,编译器直接丢弃这种Annotation。
示例:
@Retention(value= RetentionPolicy.RUNTIME)
public @interface Testable{}
// 或省略Value
@Retention(RetentionPolicy.RUNTIME)
public @interface Testable{}
2
3
4
5
6
7
# 3.2 @Target
@Target 也只能修饰一个 Annotation 定义,它用于指定被修饰的 Annotation 能用于修饰哪些程序单元。@Target 元Annotation也包含一个名为value的成员变量,该成员变量的值只能是如下几个。
ElementType.ANNOTATION_TYPE
:指定该策略的Annotation只能修饰Annotation;ElementType.CONSTRUCTOR
:指定该策略的Annotation只能修饰构造器;ElementType.FIELD
:指定该策略的Annotation只能修饰成员变量;ElementType.LOCAL_VARIABLE
:指定该策略的Annotation只能修饰局部变量;ElementType.METHOD
:指定该策略的Annotation只能修饰方法定义;ElementType.PACKAGE
:指定该策略的Annotation只能修饰包定义;ElementType.PARAMETER
:指定该策略的Annotation可以修饰参数;ElementType.TYPE
:指定该策略的Annotation可以修饰类、接口(包括注解类型)或枚举定义。
@Target(value = ElementType.FIELD)
@interface TargetTest{}
// 或省略Value
@Target(ElementType.FIELD)
@interface TargetTest{}
// 指定多个
@Target({ElementType.FIELD, ElementType.METHOD})
@interface TargetTest {}
2
3
4
5
6
7
8
9
10
11
# 3.3 @Documented
@Documented 用于指定被该元Annotation修饰的Annotation类将被javadoc工具提取成文档,如果定义Annotation类时使用了 @Documented 修饰,则所有使用该Annotation修饰的程序元素的API文档中将会包含该Annotation说明。
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
// 定义 Testable Annotation 将被 javadoc 工具提取
@Documented
public @interface Testable {
}
public class DocumentedTest {
// 使用 @Testable 修饰 info() 方法
@Testable
public void info() {
System.out.println("info 方法...");
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 3.4 @Inherited
@Inherited 元Annotation指定被它修饰的Annotation将具有继承性——如果某个类使用了@Xxx注解(定义该Annotation时使用了@Inherited修饰)修饰,则其子类将自动被@Xxx修饰。
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Inherited
public @interface Iht {
}
// 使用 @Iht 修饰的 Base 类
@Iht
class Base {
}
public class InheritableTest extends Base {
public static void main(String[] args) {
// 打印 InheritableTest 类是否有 @Inheritable 修饰
System.out.println(InheritableTest.class.isAnnotationPresent(Iht.class)); // true
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# 3.5 @Repeatable
在Java 8以前,同一个程序元素前最多只能使用一个相同类型的Annotation;如果需要在同一个元素前使用多个相同类型的Annotation,则必须使用Annotation "容器",例如下面。
@Results({@Result(name="failure", location="failed.jsp"),
@Result(name="success", location="success.jpp")})
public Action FooAction {...}
2
3
上面代码中使用了两个 @Result 注解,但由于传统Java语法不允许多次使用 @Result 修饰同一个类,因此程序必须使用 @Results 注解作为两个@Result的容器。
从Java 8开始,上面语法可以得到简化:Java 8允许使用多个相同类型的Annotation来修饰同一个类,因此上面代码可能(之所以说可能,是因为重复注解还需要对原来的注解进行改造)可简化为如下形式:
@Result(name="failure", location="failed.jsp")
@Result(name="success", location="success.jpp")
public Action FooAction {...}
2
3
开发重复注解需要使用 @Repeatable
修饰,下面通过示例示范:
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Repeatable(FkTags.class)
public @interface FkTag {
String name() default "ccj";
int age();
}
2
3
4
5
6
7
8
9
10
上面注解默认不能作为重复注解使用,如果使用两个以上的该注解修饰同一个类,编译器会报错。为了将该注解改造成重复注解,需要使用 @Repeatable 修饰该注解,使用 @Repeatable 时必须为value成员变量指定值,该成员变量的值应该是一个 "容器" 注解——该 "容器" 注解可包含多个 @FkTag,因此还需要定义如下的 "容器" 注解:
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface FkTags {
// 定义 Value 成员变量,该成员变量可接受多个 @FkTag 注解
FkTag[] value();
}
2
3
4
5
6
"容器" 注解的保留期必须比它所包含的注解的保留期更长,否则编译器会报错。
修改 @FkTag 注解例子的代码:
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Repeatable(FkTags.class)
public @interface FkTag {
String name() default "ccj";
int age();
}
2
3
4
5
6
7
8
9
10
测试类如下:
@FkTag(age = 18)
@FkTag(name = "cc", age = 20)
public class FkTagTest {
public static void main(String[] args) {
Class<FkTagTest> clazz = FkTagTest.class;
// 使用 Java8 新增的 getDeclaredAnnotationsByType() 方法获取
FkTag[] tags = clazz.getDeclaredAnnotationsByType(FkTag.class);
// 遍历修饰 FkTagTest 类的多个 @FkTag 注解
for (FkTag tag : tags) {
System.out.println(tag.name() + "-->" + tag.age());
}
// 使用传统的 getDeclaredAnnotation() 方法获取
FkTags container = clazz.getDeclaredAnnotation(FkTags.class);
System.out.println(container);
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
实际上,系统依然将两个 @FkTag 注解作为 @FkTags 的value成员变量的数组元素。只是一个简化写法。
# 四:Type Annotation
Java 8为ElementType枚举增加了 TYPE_PARAMETER、TYPE_USE
两个枚举值,这样就允许定义枚举时使用 @Target(ElementType.TYPE_USE)
修饰,这种注解被称为 Type Annotation(类型注解)
,Type Annotation 可用在任何用到类型的地方。
从Java 8开始,Type Annotation 可以在任何用到类型的地方使用。比如,允许在如下位置使用Type Annotation:
- 创建对象(用new关键字创建)。
- 类型转换。
- 使用implements实现接口。
- 使用throws声明抛出异常。
上面这些情形都会用到类型,因此都可以使用类型注解来修饰。
// 定义一个简单的 Type Annotation,不带任何成员变量
@Target(ElementType.TYPE_USE)
@interface NotNull {
}
public class TypeAnnotationTest implements @NotNull /* implements 时使用 Type Annotation*/ Serializable {
// 方法形参中使用 Type Annotation
public static void main(@NotNull String[] args)
// throws 时使用 Type Annotation
throws @NotNull FileNotFoundException {
Object obj = "ccj";
// 强制类型转换时使用 Type Annotation
String str = (@NotNull String) obj;
// 创建对象时使用 Type Annotation
Object win = new @NotNull StringBuilder();
}
public void foo(List<@NotNull String> info) {
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# 五:参考文献
- 《疯狂Java讲义(第三版) - 李刚》