Annotation:注解 ,又称为 Java 标注,在 jdk 1.5 中引入。在 Java 中,注解可以注解在 类、方法、变量、参数 等上面。
java 内置注解
java 内置了一套注解注解,共有 7 个,3 个在 java.lang 中,剩下 4 个在 java.lang.annotation 中。
作用在代码的注解 — 位于 java.lang 包:
- @Override:检查该方法是否是重写方法。如果发现其父类,或者是引用的接口中并没有该方法时,会报编译错误
- @Deprecated:标记过时方法。如果使用该方法,会报编译警告
- @SuppressWarnings:指示编译器去忽略注解中声明的警告
- unchecked:运行时异常,是 RuntimeException 的子类,不需要在代码中显式地捕获 unchecked 异常做处理
- unused:抑制变量未被使用的警告提示
- deprecation:抑制不显示使用不赞成使用的类或方法时的警告
- resource:抑制与使用 Closeable 类型资源相关的警告
- path:抑制在类路径,原文件路径中有不存在的路径的警告
- serial:抑制某类实现 Serializable,但没有定义 serialVersionUID,这个需要但是不必须的字段的警告
- all:抑制全部类型的警告
四个元注解:元注解,指作用在其他注解上的注解,位于 java.lang.annotation 包中
- @Retention(RetentionPolicy.CLASS):标识这个注解怎么保存
- RetentionPolicy.SOURCE:Annotation 仅存在于编译器处理期间,用于语法检查,不会保存进 .class
- RetentionPolicy.CLASS:编译器会将 Annotation 记录在 .class 文件,但不可以通过反射获取,属于 Annotation 的默认行为
- RetentionPolicy.RUNTIME:编译器会将该注解标识的注解保存在 .class 文件中,可以在运行时通过反射访问
- @Target(ElementType.TYPE):指定注解的作用类型,默认情况下,Annotation 可以修饰任何元素
- ElementType.TYPE:类、接口(包括注释类型)或枚举类型
- ElementType.FIELD:字段声明
- ElementType.METHOD:方法
- ElementType.CONSTRUCTOR:构造方法声明
- ElementType.PARAMETER:方法参数
- ElementType.CONSTRUCTOR:构造方法
- ElementType.LOCAL_VARIABLE:局部变量
- ElementType.ANNOTATION_TYPE:注释类型声明
- ElementType.PACKAGE:包声明
- @Documented:标记这些注解是否包含在用户文档中
- @Inherited:标记这个注解是继承于哪个注解类(默认 注解并没有继承于任何子类);即假如 @WW 注解被元注解 @Inherited 修饰,把 @WW 添加在类 Base 上,则 Base 的所有子类也将默认使用 @WW 注解
jdk 7 开始支持,额外增加了 3 个注解:
- @SafeVarargs:Java 7 开始支持,去除“堆污染”警告,忽略任何使用参数为泛型变量的方法或构造函数调用产生的警告
- 堆污染:把一个不带泛型的对象赋给一个带泛型的变量时就会发生堆污染
- @FunctionalInterface:Java 8 开始支持,标识一个匿名函数或函数式接口
- 如果接口中只有一个抽象方法(可以包含多个default方法或static方法),就是函数式接口
- @Repeatable:Java 8 开始支持,标识某注解可以在同一个声明上使用多次
自定义注解
注解分为两种:
- 标记:没有成员变量的注解,这种注解仅用自身的存在与否来提供信息,如 @Override 等
- 元数据注解:包含成员变量的注解,这些注解能够提供更多的元数据信息
注解定义:
- 使用 @interface 关键字
- 使用 @Retention 和 @Target 注解进行约束
- 以无形参的方法形式来声明 Annotation 的成员变量,方法名和返回值分别定义了成员变量名称和类型,同时可以使用 default 关键字设置默认值。
- 没有设置初始值的参数在使用时必须提供,而有初始值的变量可以根据需求设置
@Rentention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface MyTag{
//定义两个成员变量,以方法的形式定义
String name();
int age() default 32;
}
- 没有设置初始值的参数在使用时必须提供,而有初始值的变量可以根据需求设置
提取 Annotation 信息
使用了 Annotation 修饰了类、方法、成员变量等程序元素之后,注解并不会自己生效,必须由开发者通过 API 提取并处理注解信息。
Annotation 接口是所有注解的父接口,因此使用时可以先通过反射获取Annotation,然后将Annotation 转换成具体的注解类,再调用注解类定义的方法获取元数据信息。
获取 Annotation :注意,只有生命周期为 RetentionPolicy.RUNTIME 的注解才能通过反射获取
Class 类中获取 Annotation 的方法:
- public Annotation[] Class.getAnnotations():获取全部的 Annotation
- public A getAnnotation(Class annotationClass):获取某个注解类的注解
java.lang.reflect.AccessibleObject 类的子类(Constructor、Method、Field)获取 Annotation 的方法:
T getAnnotation(Class annotationClass):返回修饰该程序元素的指定类型的注解,不存在返回 null T getDeclaredAnnotation(Class annotationClass):返回直接修饰该程序元素的指定类型的注解,不存在则返回 null(java8新增) - Annotation[] getDeclaredAnnotations():返回直接存在于此元素上的所有注解
- boolean isAnnotationPresent (Class< ? extends Annotation> annotationClass):如果指定类型的注解存在于此元素上,则返回 true
java 8新增重复注解功能,因此产生了如下的两个方法:
T[] getAnnotationsByType(Class annotationClass):返回修饰该程序元素的指定类型的多个注解,不存在则返回 null T[] getDeclaredAnnotationsByType(Class annotationClass):返回直接修饰该程序元素的指定类型的多个注解,不存在则返回 null ```java / 自定义 @Value 注解 / @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.FIELD) public @interface Value { String value() default “”; }
/ User类 / public class User implements Serializable { @Value(“张三”) @Deprecated private String name; @Value(“18”) private Integer age; @Value(“true”) private boolean man; @Value(“18.75”) private Double money; / setter / }
/ 测试 / public static void main(String[] args) throws Exception { // 1. 获取Class实例 Class<?> clazz = Class.forName(“top.songfang.refelect.User”); // 2. 创建对象 User user = (User) clazz.getDeclaredConstructor().newInstance(); // 3. 获取所有的属性 Field[] fields = clazz.getDeclaredFields(); for (Field field : fields) { // 获取所有注解,然后选择注解中的 Value 注解的实例 Annotation[] annotations = field.getDeclaredAnnotations(); String value = null; for (Annotation annotation : annotations) { // 判断当前注解是否是 @Value 注解的实例 if (annotation instanceof Value){ value = ((Value)annotation).value(); } } // 或者直接获取 Value 注解 // Value annotation = field.getAnnotation(Value.class); // String value = annotation.value();
// 获取属性类型
Class<?> type = field.getType();
// 获取属性的名称
String name = field.getName();
// 构造set方法名
String set = "set" + (name.length() == 1 ?
name.toUpperCase() :
name.substring(0, 1).toUpperCase() + name.substring(1));
// 获取 set 方法
Method method = clazz.getDeclaredMethod(set, type);
if ("string".equalsIgnoreCase(field.getType().getSimpleName())) {
method.invoke(user, value);
} else if ("integer".equalsIgnoreCase(field.getType().getSimpleName()) && value.matches("\\d+")) {
method.invoke(user, Integer.parseInt(value));
} else if ("double".equalsIgnoreCase(field.getType().getSimpleName()) && value.matches("\\d+(\\.\\d{1,2})?")) {
method.invoke(user, Double.parseDouble(value));
} else if ("boolean".equalsIgnoreCase(field.getType().getSimpleName())){
method.invoke(user, "true".equalsIgnoreCase(value));
} else {
throw new RuntimeException("属性无法完成转换!");
}
}
System.out.println(user);
}
<a name="goXOg"></a>
#### 重复注解
java 8 之前注解不允许重复,因此如果需要使用重复注解,可以使用注解容器,即:
1. 新定义一个注解如 @Container
1. 然后使这个注解的成员变量为 @Repeat 注解的数组,这样可以通过使用 @Container 间接使用 @Repeat 注解的重复
**注:”容器“注解的保留期Retention必须比它所包含注解的保留期更长,否则编译报错**
```java
/* 需要重复的注解 */
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Repeat {
String value() default "";
}
/* 注解容器 */
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Container {
Repeat[] value() default {};
}
/* 容器注解使用 */
@Container({@Repeat("世界"),@Repeat("无垠")})
public class User implements Serializable {
}
/* 获取重复注解 */
public static void main(String[] args) {
// 直接获取指定注解
Repeat[] value = User.class.getAnnotation(Container.class).value();
for (Repeat repeat : value) {
System.out.println(repeat.value());
}
// 从全部注解中筛选
Annotation[] annotations = User.class.getAnnotations();
for (Annotation annotation : annotations) {
if (annotation instanceof Container){
for (Repeat repeat : ((Container) annotation).value()) {
System.out.println(repeat);
}
}
}
}
/* 输出 */
@top.songfang.reflect.Repeat(value="世界")
@top.songfang.reflect.Repeat(value="无垠")
java 8 之后,新增了元注解 @Repeatable ,用来开发重复注解,开发步骤:
- 定义重复注解,@Repeatable注解中的 value 用来指示容器注解
- 编写容器注解 ```java / 定义重复注解 / @Repeatable(value = Combination.class) @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) public @interface Repeat { String value() default “”; }
/ 容器注解 / @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) public @interface Combination { Repeat[] value(); }
/ 使用 / @Repeat(“世界”) @Repeat(“天才”) public class New { public static void main(String[] args) { // java 8 推荐的方式 Repeat[] repeats = New.class.getAnnotationsByType(Repeat.class); for (Repeat repeat : repeats) { System.out.println(repeat.value()); } } }
/ 输出 / 世界 天才
<a name="GZe67"></a>
#### 类型注解
java 8 之前,注解只能用在包、类、构造器、方法、成员变量、参数、局部变量。<br />java 8 之后,java 8 新增类型注解(Type Annotation),可以作用在创建对象(通过new创建)、类型转换、使用implements实现接口、使用throws声明抛出异常的位置。
- java 为 ElementType 枚举增加了 TYPE_PARAMETER、TYPE_USE 两个枚举值
- @Target(TYPE_USE) 修饰的注解称为 Type Annotation(类型注解),Type Annotation 可以用在任何用到类型的地方
```java
/* 定义一个类型注解 @NotNull */
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE_USE)
public @interface NotNull {
String value() default "";
}
/* 使用 */
//implements实现接口中使用Type Annotation
public class Test implements @NotNull("Serializable")Serializable {
// 泛型中使用 Type Annotation
// 抛出异常中使用 Type Annotation
public void test(List<@NotNull String> list) throws @NotNull(value = "ClassNotFoundException") ClassNotFoundException{
// 创建对象中使用 Type Annotation
Object obj = new @NotNull String("annotation.Test");
// 强制类型转换中使用 Type Annotation
String str = (@NotNull String) obj;
}
}
/* 编写处理注解的处理器 */
public class NotNullProcessor {
public static void process(String className) throws ClassNotFoundException{
try {
Class<?> clazz = Class.forName(className);
// 获取类继承的、带注解的接口
AnnotatedType[] interfaces = clazz.getAnnotatedInterfaces();
print(interfaces);
Method method = clazz.getMethod("test", List.class);
// 获取方法上抛出的带注解的异常
AnnotatedType[] annotatedTypes = method.getAnnotatedExceptionTypes();
print(annotatedTypes);
} catch (NoSuchMethodException e) {
e.printStackTrace();
}
}
private static void print(AnnotatedType[] array){
for (AnnotatedType annotatedType : array) {
// 1.获取基础类型
Type type = annotatedType.getType();
// 2.获取注解
Annotation[] annotations = annotatedType.getAnnotations();
System.out.println(type);
for (Annotation annotation : annotations) {
System.out.println(annotation);
}
}
}
public static void main(String[] args) throws ClassNotFoundException {
NotNullProcessor.process("top.songfang.refelect.type.Test");
}
}
java 8 提供 AnnotatedType 接口,该接口用来代表被注解修饰的类型,继承了 AnnotatedElement 接口,同时存在一个 public Type getType() 方法,用于返回注解修饰的类型。
获取 AnnotatedType:
- Class.getAnnotatedInterfaces():获取类继承的、带注解的接口
- Method.getAnnotatedExceptionTypes():获取方法上抛出的带注解的异常