Annotation:注解 ,又称为 Java 标注,在 jdk 1.5 中引入。在 Java 中,注解可以注解在 类、方法、变量、参数 等上面。

java 内置注解

java 内置了一套注解注解,共有 7 个,3 个在 java.lang 中,剩下 4 个在 java.lang.annotation 中。

作用在代码的注解 — 位于 java.lang 包:

  1. @Override:检查该方法是否是重写方法。如果发现其父类,或者是引用的接口中并没有该方法时,会报编译错误
  2. @Deprecated:标记过时方法。如果使用该方法,会报编译警告
  3. @SuppressWarnings:指示编译器去忽略注解中声明的警告
    • unchecked:运行时异常,是 RuntimeException 的子类,不需要在代码中显式地捕获 unchecked 异常做处理
    • unused:抑制变量未被使用的警告提示
    • deprecation:抑制不显示使用不赞成使用的类或方法时的警告
    • resource:抑制与使用 Closeable 类型资源相关的警告
    • path:抑制在类路径,原文件路径中有不存在的路径的警告
    • serial:抑制某类实现 Serializable,但没有定义 serialVersionUID,这个需要但是不必须的字段的警告
    • all:抑制全部类型的警告

四个元注解:元注解,指作用在其他注解上的注解,位于 java.lang.annotation 包中

  1. @Retention(RetentionPolicy.CLASS):标识这个注解怎么保存
    • RetentionPolicy.SOURCE:Annotation 仅存在于编译器处理期间,用于语法检查,不会保存进 .class
    • RetentionPolicy.CLASS:编译器会将 Annotation 记录在 .class 文件,但不可以通过反射获取,属于 Annotation 的默认行为
    • RetentionPolicy.RUNTIME:编译器会将该注解标识的注解保存在 .class 文件中,可以在运行时通过反射访问
  2. @Target(ElementType.TYPE):指定注解的作用类型,默认情况下,Annotation 可以修饰任何元素
    • ElementType.TYPE:类、接口(包括注释类型)或枚举类型
    • ElementType.FIELD:字段声明
    • ElementType.METHOD:方法
    • ElementType.CONSTRUCTOR:构造方法声明
    • ElementType.PARAMETER:方法参数
    • ElementType.CONSTRUCTOR:构造方法
    • ElementType.LOCAL_VARIABLE:局部变量
    • ElementType.ANNOTATION_TYPE:注释类型声明
    • ElementType.PACKAGE:包声明
  3. @Documented:标记这些注解是否包含在用户文档中
  4. @Inherited:标记这个注解是继承于哪个注解类(默认 注解并没有继承于任何子类);即假如 @WW 注解被元注解 @Inherited 修饰,把 @WW 添加在类 Base 上,则 Base 的所有子类也将默认使用 @WW 注解

jdk 7 开始支持,额外增加了 3 个注解:

  • @SafeVarargs:Java 7 开始支持,去除“堆污染”警告,忽略任何使用参数为泛型变量的方法或构造函数调用产生的警告
    • 堆污染:把一个不带泛型的对象赋给一个带泛型的变量时就会发生堆污染
  • @FunctionalInterface:Java 8 开始支持,标识一个匿名函数或函数式接口
    • 如果接口中只有一个抽象方法(可以包含多个default方法或static方法),就是函数式接口
  • @Repeatable:Java 8 开始支持,标识某注解可以在同一个声明上使用多次


自定义注解

注解分为两种:

  • 标记:没有成员变量的注解,这种注解仅用自身的存在与否来提供信息,如 @Override 等
  • 元数据注解:包含成员变量的注解,这些注解能够提供更多的元数据信息

注解定义:

  1. 使用 @interface 关键字
  2. 使用 @Retention 和 @Target 注解进行约束
  3. 以无形参的方法形式来声明 Annotation 的成员变量,方法名和返回值分别定义了成员变量名称和类型,同时可以使用 default 关键字设置默认值。
    • 没有设置初始值的参数在使用时必须提供,而有初始值的变量可以根据需求设置
      1. @Rentention(RetentionPolicy.RUNTIME)
      2. @Target(ElementType.METHOD)
      3. public @interface MyTag{
      4. //定义两个成员变量,以方法的形式定义
      5. String name();
      6. int age() default 32;
      7. }

提取 Annotation 信息

使用了 Annotation 修饰了类、方法、成员变量等程序元素之后,注解并不会自己生效,必须由开发者通过 API 提取并处理注解信息。
Annotation 接口是所有注解的父接口,因此使用时可以先通过反射获取Annotation,然后将Annotation 转换成具体的注解类,再调用注解类定义的方法获取元数据信息。

获取 Annotation :注意,只有生命周期为 RetentionPolicy.RUNTIME 的注解才能通过反射获取

Class 类中获取 Annotation 的方法:

  1. public Annotation[] Class.getAnnotations():获取全部的 Annotation
  2. public A getAnnotation(Class annotationClass):获取某个注解类的注解

java.lang.reflect.AccessibleObject 类的子类(Constructor、Method、Field)获取 Annotation 的方法:

  1. T getAnnotation(Class annotationClass):返回修饰该程序元素的指定类型的注解,不存在返回 null
  2. T getDeclaredAnnotation(Class annotationClass):返回直接修饰该程序元素的指定类型的注解,不存在则返回 null(java8新增)
  3. Annotation[] getDeclaredAnnotations():返回直接存在于此元素上的所有注解
  4. boolean isAnnotationPresent (Class< ? extends Annotation> annotationClass):如果指定类型的注解存在于此元素上,则返回 true

java 8新增重复注解功能,因此产生了如下的两个方法:

  1. T[] getAnnotationsByType(Class annotationClass):返回修饰该程序元素的指定类型的多个注解,不存在则返回 null
  2. 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();

  1. // 获取属性类型
  2. Class<?> type = field.getType();
  3. // 获取属性的名称
  4. String name = field.getName();
  5. // 构造set方法名
  6. String set = "set" + (name.length() == 1 ?
  7. name.toUpperCase() :
  8. name.substring(0, 1).toUpperCase() + name.substring(1));
  9. // 获取 set 方法
  10. Method method = clazz.getDeclaredMethod(set, type);
  11. if ("string".equalsIgnoreCase(field.getType().getSimpleName())) {
  12. method.invoke(user, value);
  13. } else if ("integer".equalsIgnoreCase(field.getType().getSimpleName()) && value.matches("\\d+")) {
  14. method.invoke(user, Integer.parseInt(value));
  15. } else if ("double".equalsIgnoreCase(field.getType().getSimpleName()) && value.matches("\\d+(\\.\\d{1,2})?")) {
  16. method.invoke(user, Double.parseDouble(value));
  17. } else if ("boolean".equalsIgnoreCase(field.getType().getSimpleName())){
  18. method.invoke(user, "true".equalsIgnoreCase(value));
  19. } else {
  20. throw new RuntimeException("属性无法完成转换!");
  21. }
  22. }
  23. System.out.println(user);

}

  1. <a name="goXOg"></a>
  2. #### 重复注解
  3. java 8 之前注解不允许重复,因此如果需要使用重复注解,可以使用注解容器,即:
  4. 1. 新定义一个注解如 @Container
  5. 1. 然后使这个注解的成员变量为 @Repeat 注解的数组,这样可以通过使用 @Container 间接使用 @Repeat 注解的重复
  6. **注:”容器“注解的保留期Retention必须比它所包含注解的保留期更长,否则编译报错**
  7. ```java
  8. /* 需要重复的注解 */
  9. @Retention(RetentionPolicy.RUNTIME)
  10. @Target(ElementType.TYPE)
  11. public @interface Repeat {
  12. String value() default "";
  13. }
  14. /* 注解容器 */
  15. @Retention(RetentionPolicy.RUNTIME)
  16. @Target(ElementType.TYPE)
  17. public @interface Container {
  18. Repeat[] value() default {};
  19. }
  20. /* 容器注解使用 */
  21. @Container({@Repeat("世界"),@Repeat("无垠")})
  22. public class User implements Serializable {
  23. }
  24. /* 获取重复注解 */
  25. public static void main(String[] args) {
  26. // 直接获取指定注解
  27. Repeat[] value = User.class.getAnnotation(Container.class).value();
  28. for (Repeat repeat : value) {
  29. System.out.println(repeat.value());
  30. }
  31. // 从全部注解中筛选
  32. Annotation[] annotations = User.class.getAnnotations();
  33. for (Annotation annotation : annotations) {
  34. if (annotation instanceof Container){
  35. for (Repeat repeat : ((Container) annotation).value()) {
  36. System.out.println(repeat);
  37. }
  38. }
  39. }
  40. }
  41. /* 输出 */
  42. @top.songfang.reflect.Repeat(value="世界")
  43. @top.songfang.reflect.Repeat(value="无垠")

java 8 之后,新增了元注解 @Repeatable ,用来开发重复注解,开发步骤:

  1. 定义重复注解,@Repeatable注解中的 value 用来指示容器注解
  2. 编写容器注解 ```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()); } } }

/ 输出 / 世界 天才

  1. <a name="GZe67"></a>
  2. #### 类型注解
  3. java 8 之前,注解只能用在包、类、构造器、方法、成员变量、参数、局部变量。<br />java 8 之后,java 8 新增类型注解(Type Annotation),可以作用在创建对象(通过new创建)、类型转换、使用implements实现接口、使用throws声明抛出异常的位置。
  4. - java 为 ElementType 枚举增加了 TYPE_PARAMETER、TYPE_USE 两个枚举值
  5. - @Target(TYPE_USE) 修饰的注解称为 Type Annotation(类型注解),Type Annotation 可以用在任何用到类型的地方
  6. ```java
  7. /* 定义一个类型注解 @NotNull */
  8. @Retention(RetentionPolicy.RUNTIME)
  9. @Target(ElementType.TYPE_USE)
  10. public @interface NotNull {
  11. String value() default "";
  12. }
  13. /* 使用 */
  14. //implements实现接口中使用Type Annotation
  15. public class Test implements @NotNull("Serializable")Serializable {
  16. // 泛型中使用 Type Annotation
  17. // 抛出异常中使用 Type Annotation
  18. public void test(List<@NotNull String> list) throws @NotNull(value = "ClassNotFoundException") ClassNotFoundException{
  19. // 创建对象中使用 Type Annotation
  20. Object obj = new @NotNull String("annotation.Test");
  21. // 强制类型转换中使用 Type Annotation
  22. String str = (@NotNull String) obj;
  23. }
  24. }
  25. /* 编写处理注解的处理器 */
  26. public class NotNullProcessor {
  27. public static void process(String className) throws ClassNotFoundException{
  28. try {
  29. Class<?> clazz = Class.forName(className);
  30. // 获取类继承的、带注解的接口
  31. AnnotatedType[] interfaces = clazz.getAnnotatedInterfaces();
  32. print(interfaces);
  33. Method method = clazz.getMethod("test", List.class);
  34. // 获取方法上抛出的带注解的异常
  35. AnnotatedType[] annotatedTypes = method.getAnnotatedExceptionTypes();
  36. print(annotatedTypes);
  37. } catch (NoSuchMethodException e) {
  38. e.printStackTrace();
  39. }
  40. }
  41. private static void print(AnnotatedType[] array){
  42. for (AnnotatedType annotatedType : array) {
  43. // 1.获取基础类型
  44. Type type = annotatedType.getType();
  45. // 2.获取注解
  46. Annotation[] annotations = annotatedType.getAnnotations();
  47. System.out.println(type);
  48. for (Annotation annotation : annotations) {
  49. System.out.println(annotation);
  50. }
  51. }
  52. }
  53. public static void main(String[] args) throws ClassNotFoundException {
  54. NotNullProcessor.process("top.songfang.refelect.type.Test");
  55. }
  56. }

java 8 提供 AnnotatedType 接口,该接口用来代表被注解修饰的类型,继承了 AnnotatedElement 接口,同时存在一个 public Type getType() 方法,用于返回注解修饰的类型。

获取 AnnotatedType:

  1. Class.getAnnotatedInterfaces():获取类继承的、带注解的接口
  2. Method.getAnnotatedExceptionTypes():获取方法上抛出的带注解的异常