Java语言基础(SE)-第十一节 注解

注解简介

  1. 格式

     public @interface 注解名称{
         属性列表;
     }
    
  2. 分类
    1. 大致分为三类:自定义注解、JDK内置注解、还有第三方框架提供的注解
    2. JDK内置注解,比如@Override检验方法重载,@Deprecated标识方法过期等。第三方框架定义的注解比如SpringMVC的@Controller等。
  3. 使用位置
    1. 实际开发中,注解常常出现在类、方法、成员变量、形参位置。当然还有其他位置,这里不提及。
  4. 作用
    1. 如果说注释是写给人看的,那么注解就是写给程序看的。它更像一个标签,贴在一个类、一个方 法或者字段上。它的目的是为当前读取该注解的程序提供判断依据。比如程序只要读到加了 @Test的方法,就知道该方法是待测试方法,又比如@Before注解,程序看到这个注解,就知道该 方法要放在@Test方法之前执行。
  5. 级别
    1. 注解和类、接口、枚举是同一级别的。

注解的本质

  1. 定义注解ZHAnnotation

     public @interface ZHAnnotation {
     }
    
  2. 编译后得到字节码 文件 ZHAnnotation.class
  3. 通过XJad工具反编译ZHAnnotation.class

     import java.lang.annotation.Annotation
     public interface ZHAnnotation extends Annotation {
     }
    
  4. 发现,@interface变成了interface,而且自动继承了Annotation
  5. 既然注解的本质是个接口,那么我们自然可以在里面写方法

     public @interface ZHAnnotation {
         String getValue();
     }
    

注解的属性

  1. 尽管注解看起来本质是接口,但是还是跟接口有所不同
  2. 注解里面声明的方法叫做属性,可以进行直接赋值

     @ZHAnnotation(getValue = "annotation on class")
     public class Demo {
         @ZHAnnotation(getValue = "annotation on field")
         public String name;
         @ZHAnnotation(getValue = "annotation on method")
         public void hello();
         //使用默认值
         @ZHAnnotation()
         public void defaultMethod();
     }
    
  3. 可以为属性指定默认值:

     public @interface ZHAnnotation {
         String getValue() default "no description";
     }
    

反射读取注解信息

  1. 只要用到注解,必然有三角关系:定义注解,使用注解,读取注解
  2. 以下通过反射来读取注解

     public class TestDemo {
         public static void main (String[] args) throw Exception {
             //获取类上的注解
             Class<Demo> clas = Demo.class;
             ZHAnnotation  annOnClass = clas.getAnnotation(ZHAnnotation.class);
             System.out.pringln(annOnClass.getValue());
             //获取成员变量上的注解
             Field  name  = clas.getField("name");
             ZHAnnotation  annOnField = name.getAnnotation(ZHAnnotation.class);
             System.out.pringln(annOnField.getValue());
             //获取方法上的注解
             Method  hello  = clas.getMethod("hello",null);
             ZHAnnotation  annOnMethod = hello.getAnnotation(ZHAnnotation.class);
             System.out.pringln(annOnMethod.getValue());
         }
     }
    
  3. Class、Method、Field对象都有个getAnnotation(),可以获取各自位置的注解信息。
  4. 但是控制台提示“空指针 异常”,IDEA提示:Annotation’MyAnnotation.class’isnot retained for reflective。直译的话就是:注解MyAnnotation并没有为反射保留
  5. 这是因为注解其实有所谓“保留策略”的说法。注解通过保留策略,控制自己可以保留到哪个阶段。保留策略也是通过注解实现,它属于元 注解,也叫元数据。

元注解

  1. 所谓元注解,就是加在注解上的注解。作为普通程序员,常用的就是:@Documented、@Target、@Retention
  2. @Documented: 生成文档使用,标明是否生成javadoc文档
  3. @Target
    1. 加在注解上,限定该注解的使用位置。不写的话,默认各个位置都是可以的。如果需要限定 注解的使用位置,可以在自定义的注解上使用该注解。
    2. 类、属性、方法,等其他地方
  4. @Retention(注解的保留策略)
    1. 注解必须有保留策略,有三种:SOURCE/ClASS/RUNTIME
      1. SOURCE 文件是java源码阶段—磁盘中
      2. ClASS 源码文件被编译成class字节码时—-磁盘中
      3. RUNTIME 应用程序启动被加载到运行内存时—-运行内存中
    2. 因为别的代码要读取某个注解肯定是从运行内存中读取,因此通常是RUNTIME
  5. 举例

     //生成文档使用,标明是否生成javadoc文档,不重要
     @Documented
     /**
      * 注解的保留策略,注解必须有保留策略:SOURCE/ClASS/RUNTIME
      * SOURCE 文件是java源码阶段---磁盘中
      * ClASS 源码文件被编译成class字节码时----磁盘中
      * RUNTIME 应用程序启动被加载到运行内存时----运行内存中
      * 因为别的代码要读取某个注解肯定是从运行内存中读取,因此是RUNTIME
      */
     @Retention(RetentionPolicy.RUNTIME)
     // 当前注解呗应用到哪个地方:类、属性、方法,还是其他
     @Target(ElementType.FIELD)
     public @interface ZHAnnotation {
         String getValue() default "no description";
     }
    

注解属性的数据类型

  1. 注解的属性就是注解里面定义的方法,可以把他想象成跟类里面的属性一样:int name
  2. 那么既然是属性肯定要有属性的数据类型
  3. 数据类型如下
    1. 八种基本数据类型
    2. String
    3. 枚举
    4. Class
    5. 注解类型
    6. 以上类型的一维数组
     @Retention(RetentionPolicy.RUNTIME)
     public @interface ZHAnnotation {
            
         int intValue();
         double doubleValue();
         String name();
         //枚举
         CityEnum cityName();
         //注解
         ZHAnnotation2 annotation2();
         //一维数组
         int[] intValueArray();
         String[] names();
     }
    

注解的特殊属性

  1. value属性
    1. 如果注解的属性只有一个,且叫value,那么使用该注解时,可以不用指定属性名,因为默认就是给value赋值:
    2. 注解如果属性是value,且其他属性有默认值,同样可以省略

       @Retention(RetentionPolicy.RUNTIME)
       public @interface ZHAnnotation {
                  
           int value();
           double doubleValue() default "123";
       }
              
       @ZHAnnotation("annotation on class")
       public class Demo {
           @ZHAnnotation("annotation on field")
           public String name;
       }
      
  2. 数组属性
    1. 如果数组的元素只有一个,可以省略{}:
     @ZHAnnotation(
         intValueArray=1,
         names="zh"
     )
     public class Demo {
     }
    

@Repeatable注解

  1. java8新增了注解@Repeatable
  2. 该注解就是可以让一个注解在一个地方重复使用

自定义RepeaDemo注解

  1. 新建注解RepeaDemo,暂时不添加@Repeatable:

     @Target(value = {ElementType.FIELD})
     @Retention(RetentionPolicy.RUNTIME)
     @Documented
     public @interface RepeaDemo {
         String id();
         String name();
     }
    
  2. 测试类

     public class TestRepeatable {
         @RepeaDemo(id = "1001",name = "荔枝")
         private String fruit1;
        
         @RepeaDemo(id = "1002",name = "葡萄")
         private String fruit2;
        
         public static void demo1(){
             Class<?> a = TestRepeatable.class;
             Field[] fruits = a.getDeclaredFields();
             Arrays.stream(fruits).forEach(f->{
                 if(f.isAnnotationPresent(RepeaDemo.class)){
                     RepeaDemo anno = f.getAnnotation(RepeaDemo.class);
                     String id = anno.id();
                     String name = anno.name();
                     System.out.printf("水果id:%s,水果名称:%s%n",id,name);
                 }
             });
         }
         public static void main(String[] args) {
             demo1();
         }
     }
    
    1. 打印结果:

       水果id:1001,水果名称:荔枝
       水果id:1002,水果名称:葡萄
      
  3. 问题:如果希望在字段上增加多个注解,那么会提示Duplicate annotation.错误:

     //会报错
     public class TestRepeatable {
         //重复注解
         @RepeaDemo(id = "1001",name = "荔枝")
         @RepeaDemo(id = "1003",name = "苹果")
         private String fruit1;
         ...
     }
    
    1. 会提示错误

自定义RepeaDemos

  1. 自定义一个RepeaDemos注解,用于存储@RepeaDemo注解数组,就可以达到多个注解的使用了,如下:

     @Target(value = {ElementType.FIELD})
     @Retention(RetentionPolicy.RUNTIME)
     @Documented
     public @interface RepeaDemos {
         RepeaDemo[] repeaDemos();
     }
    
  2. 测试:

     public class TestRepeatable {
         @RepeaDemos(repeaDemos = {
             @RepeaDemo(id = "1001",name = "荔枝"),
             @RepeaDemo(id = "1003",name = "沃柑")
         })
         private String fruit1;
         @RepeaDemo(id = "1002",name = "葡萄")
         private String fruit2;
         public static void demo1(){
             Class<?> a = TestRepeatable.class;
             Field[] fruits = a.getDeclaredFields();
             Arrays.stream(fruits).forEach(f->{
                 if(f.isAnnotationPresent(RepeaDemos.class)){
                     RepeaDemos anno = f.getAnnotation(RepeaDemos.class);
                     RepeaDemo[] res= anno.repeaDemos();
                     Arrays.stream(res).forEach(r-> System.out.printf("水果id:%s,水果名称:%s%n",r.id(),r.name()));
                 }
                 if(f.isAnnotationPresent(RepeaDemo.class)){
                     RepeaDemo anno = f.getAnnotation(RepeaDemo.class);
                     String id = anno.id();
                     String name = anno.name();
                     System.out.printf("水果id:%s,水果名称:%s%n",id,name);
                 }
             });
         }
         public static void main(String[] args) {
             demo1();
         }
     }
    
    1. 打印结果

       水果id:1001,水果名称:荔枝
       水果id:1003,水果名称:沃柑
       水果id:1002,水果名称:葡萄
      

@Repeatable注解使用:

  1. 对于上述注解使用,@Repeatable注解的作用就是使得@RepeaDemo注解可以多个添加在字段上,而最外层无须包裹@RepeaDemos注解,当然,@RepeaDemos注解任然需要保留,修改如下:
  2. 修改@RepeaDemos注解(注意@RepeaDemos注解,必须改为value()才可以)

     @Target(value = {ElementType.FIELD})
     @Retention(RetentionPolicy.RUNTIME)
     @Documented
     public @interface RepeaDemos {
         RepeaDemo[] value();
     }
    
  3. 修改@RepeaDemo注解,增加@Repeatable,参数为RepeaDemos.class:

     @Target(value = {ElementType.FIELD})
     @Retention(RetentionPolicy.RUNTIME)
     @Repeatable(RepeaDemos.class)
     @Documented
     public @interface RepeaDemo {
         String id();
         String name();
     }
    
  4. 测试

     public class TestRepeatable {
         @RepeaDemo(id = "1001",name = "荔枝"),
         @RepeaDemo(id = "1003",name = "沃柑")
         private String fruit1;
         ...同上
     }
    
    1. 测试结果,同上

总结

  1. 可见,如下两种方式,获取的结果是一致的,其实@Repeatable注解就是一种语法糖,本质上在java编译时,使用如下的第2种注解方式,本质是使用第1种
  2. 而且在使用反射获取注解内容时,只能通过RepeaDemos 即getAnnotation(RepeaDemos.class)获取

     //1:
     @RepeaDemos(value = {
         @RepeaDemo(id = "1001",name = "荔枝"),
         @RepeaDemo(id = "1003",name = "沃柑")
     })
     //2:
     @RepeaDemo(id = "1001",name = "荔枝")
     @RepeaDemo(id = "1003",name = "沃柑")
    
Table of Contents