概念
从 JDK 5 开始,Java 增加了对元数据(MetaData)的支持,也就是 Annotation,即注解。
注解是代码里的特殊标记,这些标记可以在编译、类加载、运行时被读取,并执行相应的处理。
通过使用注解,开发人员可以在不改变原有逻辑的情况下,在源文件中嵌入一些补充信息。
基本注解
@Override:限定重写父类方法
用于表示一个子类的方法重写了父类的方法,此注解只能修饰方法。
@Deprecated:标记已过时
用于表示某个程序元素(类、方法)已过时,当程序使用已过时的类、方法时,编译器会给出警告。
@SuppressWarnings:抑制编译器警告
被该注解修饰的元素以及它的所有子元素会取消显示指定的编译器警告。
元注解(Meta Annotation)
定义:元注解就是修饰注解的注解。
@Retention
只能用于修饰注解,用于指定被修饰的注解可以保留多长时间。
使用 @Retention
必须为其 value 赋值,其值为枚举类型 RetentionPolicy
。
- RetentionPolicy.CLASS:编译器把 Annotation 记录在 class 文件中,当运行程序时, JVM 不可获取注解信息。
- RetentionPolicy.RUNTIME:编译器把 Annotation 记录在 class 文件中,当运行程序时, JVM 可以获取注解信息,程序也可以通过反射获取该注解信息。
- RetentionPolicy.SOURCE:Annotation 只保留在源代码中,编译器直接丢弃这种注解。
@Retention(RetentionPolicy.RUNTIME)
public @interface Test {}
@Target
只能用于修饰注解,用于指定被修饰的 Annotation 能用于修饰哪些程序元素。
使用 @Target
必须为其 value 赋值,其值为枚举类型 ElementType
。
- ElementType.TYPE:指定该策略的 Annotation 可以修饰类、接口或枚举定义。
- ElementType.FIELD:指定该策略的 Annotation 只能修饰成员变量。
- ElementType.LOCAL_VARIABLE:指定该策略的 Annotation 只能修饰局部变量。
- ElementType.METHOD:指定该策略的 Annotation 只能修饰方法。
- ElementType.PARAMETER:指定该策略的 Annotation 只能修饰参数。
- ElementType.CONSTRUCTOR:指定该策略的 Annotation 只能修饰构造器。
- ElementType.PACKAGE:指定该策略的 Annotation 只能修饰包定义。
- ElementType.ANNOTATION_TYPE:指定该策略 Annotation 只能修饰 Annotation。
@Target(ElementType.TYPE)
public @interface Test {}
@Documented
@Documented
修饰的 Annotation 类将被 JavaDoc 工具提取成文档。
如果定义 Annotation 时使用 @Documented
修饰,则所有使用该 Annotation 修饰的程序元素的 API 文档中将会包含该 Annotation 说明。
@Documented
public @interface Test {}
@Inherited
@Inherited
修饰的 Annotation 类将具有继承性。
例如,@XXX 使用 @Inherited
修饰,如果某个类使用了 @XXX 注解,则其子类将自动被 @XXX 注解。
@Inherited
public @interface Test {}
Java 8 下的注解
类型注解(Type Annotation)
Java 8 为 @Target
这个元注解的 value ElementType
新增了两个枚举值
- ElementType.TYPE_USE
- ElementType.TYPE_PARAMETER
我们定义注解时就可以使用 @Target(ElementType.TYPE_USE)
来修饰,这种注解称为类型注解,可以使用在任何用到类型的地方,而不像在 Java 8 以前我们只能在定义程序元素时使用,例如定义类、定义接口等等。
// 定义类型注解
@Target(ElementType.TYPE_USE)
public @interface TypeAnnotation {}
// 使用
@TypeAnnotation
public class Test implements @TypeAnnotation Serializable {
public static void main(String[] args) {
String name = new @TypeAnnotation String("fancyluo");
}
}
可以看到,类型注解可以无处不在,这样我们就可以使用类型注解来让编译器执行更严格的代码检查,从而提高程序的健壮性。但是,Java 8 并没有为类型注解提供检查框架,需要开发者自行实现。
重复注解
在 Java 8 以前,同一个程序元素前只能使用一个相同类型的 Annotion,例如下面的例子是不行滴。
@RepeatableAnnotation(name = "阿道")
@RepeatableAnnotation(name = "fancyluo")
public class Test {}
如果需要使用多个相同类型的 Annotion,则需要定义一个 Annotation 容器来存放,容器的成员变量为相应的 Annotation 数组。
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface RepeatableAnnotations {
RepeatableAnnotation[] value();
}
接下来定义要重复使用的 Annotion,需要使用 @Repeatable
来修饰,@Repeatable
的 value 为 Annotation 容器。
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Repeatable(RepeatableAnnotations.class)
public @interface RepeatableAnnotation {
String name();
}
下面我们就可以使用相同类型的多个 Annotation 来修饰程序元素了,我们来看看 Java 8 与 Java 8 之前的写法。
// Java 8 之前
@RepeatableAnnotations({
@RepeatableAnnotation(name = "fancyluo"),
@RepeatableAnnotation(name = "阿道")
})
public class Test {}
// Java 8
@RepeatableAnnotation(name = "fancyluo")
@RepeatableAnnotation(name = "阿道")
public class Test {}L
自定义 Annotation
定义新的 Annotation 类型可以使用 @interface
关键字。
public @interface Test {}
在默认情况下,Annotation 可以修饰任何程序元素,包括类、接口、方法等等。
@Test
public class User {
@Test
private void myMethod(){}
}
还可以为 Annotation 以无形参的方法形式来声明成员变量,方法名为成员变量的名字,返回值为成员变量的类型。
public @interface Phone {
String name();
int price();
}
@Phone(name = "IPhone X", price = 8888)
public class IPhone {}
当成员变量只有一个并且名为 value
时,可以直接赋值。
public @interface Test {
String value();
}
@Test("fancyluo")
public class User {}
一旦为 Annotation 声明了成员变量,就需要在使用的时候为该成员变量赋值,也可以为其设置初始值,使用时则可以不赋值,设置初始值使用 default
关键字。
public @interface Person {
String name() default "fancyluo";
}
@Person
public class User {}
通过反射提取 Annotation 信息
从 Java 5 开始反射的 API 提供了读取运行时注解的能力,当定义的注解使用 @Retention(RetentionPolicy.RUNTIME)
修饰时,就可以在运行时使用反射提取该注解的信息。
// 定义注解
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Phone {
String name();
int price();
}
// 修饰类
@Phone(name = "IPhone X",price = 8888)
public class IPhone {}
// 获取注解信息
try {
// 使用全类名获取类
Class<?> clazz = Class.forName("com.fancyluo.test.IPhone");
// 获取类的所有注解
Annotation[] annotationArray = clazz.getAnnotations();
for (Annotation annotation : annotationArray) {
// 打印注解信息
System.out.println(annotation);
// 如果要获取注解的元数据,可以强转成具体的注解来获取
if (annotation instanceof Phone) {
Phone phone = (Phone) annotation;
System.out.println(phone.name() + ":" + phone.price());
}
}
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
Class 这个类实现了 AnnotatedElement 接口,而这个接口里面定义了一系列获取注解的方法,所以我们就可以通过 Class 类来拿到注解的信息。不仅仅是 Class 类,AnnotatedElement 是所有程序元素的父接口,例如 Class、Method、Construtor 等。
下面我们来看看 AnnotatedElement 接口定义的各个方法的含义。
// 返回该程序元素上存在的指定类型的注解,不存在则返回 null
<T extends Annotation> T getAnnotation(Class<T> var1)
// Java 8 新增,尝试获取直接修饰该程序元素的指定类型的注解,不存在则返回 null
<T extends Annotation> T getDeclaredAnnotation(Class<T> var1)
// 返回该程序元素的所有注解
Annotation[] getAnnotations()
// 返回直接修饰该程序元素的所有注解
Annotation[] getDeclaredAnnotations()
// 判断该程序元素上是否存在指定类型的注解,存在返回 true,否则返回 false
boolean isAnnotationPresent(Class<? extends Annotation> annotationClass)
// 获取修饰该程序元素、指定类型的多个 Annotation(用于 Java 8 新增的重复注解)
<T extends Annotation> T[] getAnnotationsByType(Class<T> annotationClass)
// 获取直接修饰该程序元素、指定类型的多个 Annotation(用于 Java 8 新增的重复注解)
<T extends Annotation> T[] getDeclaredAnnotationsByType(Class<T> annotationClass)
网友评论