Java语言基础(SE)-第十一节 注解
注解简介
-
格式
public @interface 注解名称{ 属性列表; }
- 分类
- 大致分为三类:自定义注解、JDK内置注解、还有第三方框架提供的注解
- JDK内置注解,比如@Override检验方法重载,@Deprecated标识方法过期等。第三方框架定义的注解比如SpringMVC的@Controller等。
- 使用位置
- 实际开发中,注解常常出现在类、方法、成员变量、形参位置。当然还有其他位置,这里不提及。
- 作用
- 如果说注释是写给人看的,那么注解就是写给程序看的。它更像一个标签,贴在一个类、一个方 法或者字段上。它的目的是为当前读取该注解的程序提供判断依据。比如程序只要读到加了
@Test
的方法,就知道该方法是待测试方法,又比如@Before
注解,程序看到这个注解,就知道该 方法要放在@Test
方法之前执行。
- 如果说注释是写给人看的,那么注解就是写给程序看的。它更像一个标签,贴在一个类、一个方 法或者字段上。它的目的是为当前读取该注解的程序提供判断依据。比如程序只要读到加了
- 级别
- 注解和类、接口、枚举是同一级别的。
注解的本质
-
定义注解ZHAnnotation
public @interface ZHAnnotation { }
- 编译后得到字节码 文件 ZHAnnotation.class
-
通过XJad工具反编译ZHAnnotation.class
import java.lang.annotation.Annotation public interface ZHAnnotation extends Annotation { }
- 发现,@interface变成了interface,而且自动继承了Annotation
-
既然注解的本质是个接口,那么我们自然可以在里面写方法
public @interface ZHAnnotation { String getValue(); }
注解的属性
- 尽管注解看起来本质是接口,但是还是跟接口有所不同
-
注解里面声明的方法叫做属性,可以进行直接赋值
@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(); }
-
可以为属性指定默认值:
public @interface ZHAnnotation { String getValue() default "no description"; }
反射读取注解信息
- 只要用到注解,必然有三角关系:定义注解,使用注解,读取注解。
-
以下通过反射来读取注解
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()); } }
- Class、Method、Field对象都有个getAnnotation(),可以获取各自位置的注解信息。
- 但是控制台提示“空指针 异常”,IDEA提示:Annotation’MyAnnotation.class’isnot retained for reflective。直译的话就是:注解MyAnnotation并没有为反射保留。
- 这是因为注解其实有所谓“保留策略”的说法。注解通过保留策略,控制自己可以保留到哪个阶段。保留策略也是通过注解实现,它属于元 注解,也叫元数据。
元注解
- 所谓元注解,就是加在注解上的注解。作为普通程序员,常用的就是:@Documented、@Target、@Retention
- @Documented: 生成文档使用,标明是否生成javadoc文档
- @Target
- 加在注解上,限定该注解的使用位置。不写的话,默认各个位置都是可以的。如果需要限定 注解的使用位置,可以在自定义的注解上使用该注解。
- 类、属性、方法,等其他地方
- @Retention(注解的保留策略)
- 注解必须有保留策略,有三种:SOURCE/ClASS/RUNTIME
- SOURCE 文件是java源码阶段—磁盘中
- ClASS 源码文件被编译成class字节码时—-磁盘中
- RUNTIME 应用程序启动被加载到运行内存时—-运行内存中
- 因为别的代码要读取某个注解肯定是从运行内存中读取,因此通常是RUNTIME
- 注解必须有保留策略,有三种:SOURCE/ClASS/RUNTIME
-
举例
//生成文档使用,标明是否生成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"; }
注解属性的数据类型
- 注解的属性就是注解里面定义的方法,可以把他想象成跟类里面的属性一样:
int name
- 那么既然是属性肯定要有属性的数据类型
- 数据类型如下
- 八种基本数据类型
- String
- 枚举
- Class
- 注解类型
- 以上类型的一维数组
@Retention(RetentionPolicy.RUNTIME) public @interface ZHAnnotation { int intValue(); double doubleValue(); String name(); //枚举 CityEnum cityName(); //注解 ZHAnnotation2 annotation2(); //一维数组 int[] intValueArray(); String[] names(); }
注解的特殊属性
- value属性
- 如果注解的属性只有一个,且叫value,那么使用该注解时,可以不用指定属性名,因为默认就是给value赋值:
-
注解如果属性是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; }
- 数组属性
- 如果数组的元素只有一个,可以省略{}:
@ZHAnnotation( intValueArray=1, names="zh" ) public class Demo { }
@Repeatable注解
- java8新增了注解@Repeatable
- 该注解就是可以让一个注解在一个地方重复使用
自定义RepeaDemo注解
-
新建注解RepeaDemo,暂时不添加@Repeatable:
@Target(value = {ElementType.FIELD}) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface RepeaDemo { String id(); String name(); }
-
测试类
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(); } }
-
打印结果:
水果id:1001,水果名称:荔枝 水果id:1002,水果名称:葡萄
-
-
问题:如果希望在字段上增加多个注解,那么会提示Duplicate annotation.错误:
//会报错 public class TestRepeatable { //重复注解 @RepeaDemo(id = "1001",name = "荔枝") @RepeaDemo(id = "1003",name = "苹果") private String fruit1; ... }
- 会提示错误
自定义RepeaDemos
-
自定义一个RepeaDemos注解,用于存储@RepeaDemo注解数组,就可以达到多个注解的使用了,如下:
@Target(value = {ElementType.FIELD}) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface RepeaDemos { RepeaDemo[] repeaDemos(); }
-
测试:
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(); } }
-
打印结果
水果id:1001,水果名称:荔枝 水果id:1003,水果名称:沃柑 水果id:1002,水果名称:葡萄
-
@Repeatable注解使用:
- 对于上述注解使用,@Repeatable注解的作用就是使得@RepeaDemo注解可以多个添加在字段上,而最外层无须包裹@RepeaDemos注解,当然,@RepeaDemos注解任然需要保留,修改如下:
-
修改@RepeaDemos注解(注意@RepeaDemos注解,必须改为value()才可以):
@Target(value = {ElementType.FIELD}) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface RepeaDemos { RepeaDemo[] value(); }
-
修改@RepeaDemo注解,增加@Repeatable,参数为RepeaDemos.class:
@Target(value = {ElementType.FIELD}) @Retention(RetentionPolicy.RUNTIME) @Repeatable(RepeaDemos.class) @Documented public @interface RepeaDemo { String id(); String name(); }
-
测试
public class TestRepeatable { @RepeaDemo(id = "1001",name = "荔枝"), @RepeaDemo(id = "1003",name = "沃柑") private String fruit1; ...同上 }
- 测试结果,同上
总结
- 可见,如下两种方式,获取的结果是一致的,其实@Repeatable注解就是一种语法糖,本质上在java编译时,使用如下的第2种注解方式,本质是使用第1种
-
而且在使用反射获取注解内容时,只能通过RepeaDemos 即getAnnotation(RepeaDemos.class)获取
//1: @RepeaDemos(value = { @RepeaDemo(id = "1001",name = "荔枝"), @RepeaDemo(id = "1003",name = "沃柑") }) //2: @RepeaDemo(id = "1001",name = "荔枝") @RepeaDemo(id = "1003",name = "沃柑")