19. Java 注解
Java 5 之后可以在源代码中嵌入一些补充信息,这种补充信息称为注解(Annotation),例如在方法覆盖中使用过的@Override 注解,注解都是**@符号开头**的。
注解并不能改变程序运行的结果,不会影响程序运行的性能。有些注解可以在编译时给用户提示或警告,有的注解可以在运行时读写字节码文件信息。
五个基本注解
无论是哪一种注解,本质上都一种数据类型,是一种接口类型。到 Java 8 为止 Java SE 提供 11 种内置注解。其中有 5 种是基本注解,它们来自于 java.lang 包。有 6 个是元注解(Meta Annotation),它们来自于 java.lang.annotation 包,自定义注解会用到元注解。
元注解就是负责注解其他的注解。
基本注解包括:@Override、@Deprecated、@SuppressWarnings、@SafeVarargs 和 @FunctionalInterface。下面逐一介绍一下。
@Override只能用于方法,子类覆盖父类方法(或者实现接口的方法)时可以 @Override 注解。编译器会检查被 @Override 注解的方法,确保该方法父类中存在的方法,否则会有编译错误。
1 | package java.lang; |
@Deprecated 用来指示 API 已经过时了
1 | package java.lang; |
@SuppressWarnings
用来抑制编译器警告,如果你确认程序中的警告没有问题,可以不用理会。但是就是不想看到这些警告,可以使用 @SuppressWarnings 注解消除这些警告。
1 |
|
取值 和 用途
“all”
to suppress all warnings
“unchecked”
告诉编译器忽略 unchecked 警告信息,如使用List,ArrayList等未进行参数化产生的警告信息。
“serial”
如果编译器出现这样的警告信息:The serializable class WmailCalendar does not declare a static final serialVersionUID field of type long
“rawtypes”
抑制检查规则:传参时也要传递带泛型的参数
“unused”
抑制未使用的代码禁止显示警告
@SafeVarargs
例如在传递可变参数,参数是非泛型集合
1 | public class HelloWorld { |
在可变参数 display 前添加 @SafeVarargs 注解。当然可用也可以使用 @SuppressWarnings(“unchecked”)注解,但@SafeVarargs注解更适合。
1 |
|
1 | package java.lang; |
@FunctionalInterface
在Java 8增加的,用于接口的注解,声明接口是函数式接口,在前面讲解Lambda表达式时介绍过函数式接口,有关@FunctionalInterface注解不再赘述。
1 |
|
六个元注解
元注解包括:@Documented、@Target、@Retention、@Inherited、@Repeatable 和 @Native。元注解是为其他注解进行说明的注解,当自定义一个新的注解类型时,其中可以使用元注解。这一节先介绍一下这几种元注解含义,下一节在自定义注解中详细介绍它们的使用的。
1. @Documented
如果在一个自定义注解中引用 @Documented 注解,那么该注解可以修饰代码元素(类、接口、成员变量和成员方法等),javadoc等工具可以提取这些注解信息。
2. @Target
@Target注解用来指定一个新注解的适用目标。@Target 注解有一个成员(value)用来设置适用目标,value 是 java.lang.annotation.ElementType 枚举类型的数组,ElementType 描述 Java 程序元素类型,它有 10 个枚举常量,如表所示。
3. @Retention
@Retention注解用来指定一个新注解的有效范围,@Retention注解有一个成员(value)用来设置保留策略,value是java.lang.annotation.RetentionPolicy枚举类型,RetentionPolicy描述注解保留策略,它有3个枚举常量,如表所示。
4. @Inherited
@Inherited注解用来指定一个新注解可以被继承。假定一个类A被该新注解修饰,那么这个A类的子类会继承该新注解。
5. @Repeatable
@Repeatable注解是Java 8新增加的,它允许在相同的程序元素中重复注释,可重复的注释必须使用 @Repeatable 进行注释。
6. @Native
@Native注解一个成员变量,指示这个变量可以被本地代码引用。常常被代码生成工具使用。
自定义注解
如果前面的 Java SE 提供的 11 个内置注解不能满足你的需求,可以自定义注解,注解本质是一种接口,它是java.lang.annotation.Annotation接口的子接口,是引用数据类型。
新建注解类
声明自定义注解可以使用 @interface 关键字实现,最简单形式的注解示例代码如下:
1 | package qy.likai.java; |
注意 关于注解源程序文件与类一样,一个源程序文件中可以声明多个注解,但只能有一个是公有访问权限的,源程序文件命名与公有访问权限的注解名一致。
Marker注解中不包含任何的成员,这种注解称为标记注解(Marked Annotation),基本注解中的@Override就属于标记注解。根据需要注解中可以包含一些成员,示例代码如下:
1 | package qy.likai.java; |
读取运行时注解信息
注解是为工具读取信息而准备的。有些工具可以读取源代码文件中的注解信息;有的可以读取字节码文件中的注解信息;有的可以在运行时读取注解信息。但是读取这些注解信息的代码都是一样的,区别只在于自定义注解中 @Retention 的保留策略不同。
读取注解信息需要反射相关API,Class类如下方法:
<A extends Annotation> A getAnnotation(Class<A> annotationClass)
:如果此元素存在annotationClass类型的注解,则返回注解,否则返回null。- Annotation[] getAnnotations():返回此元素上存在的所有注解。
- Annotation[] getDeclaredAnnotations():返回直接存在于此元素上的所有注解。与getAnnotations()区别在于该方法将不返回继承的注释。
- boolean isAnnotationPresent(Class<? extends Annotation> annotationClass):如果此元素上存在annotationClass类型的注解,则返回true,否则返回false。
- boolean isAnnotation():如果此Class对象表示一个注解类型则返回true。
1 | package qy.likai.java; |
元注解中 RetentionPolicy 的三个级别
1、RetentionPolicy.SOURCE:注解只保留在源文件,当Java文件编译成class文件的时候,注解被遗弃;
2、RetentionPolicy.CLASS:注解被保留到 class文件,但jvm加载class文件时候被遗弃,这是默认的生命周期;
3、RetentionPolicy.RUNTIME:注解不仅被保存到class文件中,jvm加载class文件之后,仍然存在;
这3个生命周期分别对应于:Java 源文件(.java文件) —> .class文件 —> 内存中的字节码。
那怎么来选择合适的注解生命周期呢?
首先要明确生命周期长度 SOURCE < CLASS < RUNTIME,所以前者能作用的地方后者一定也能作用。一般如果需要在运行时去动态获取注解信息,那只能用 RUNTIME 注解;如果要在编译时进行一些预处理操作,比如生成一些辅助代码(如 ButterKnife),就用 CLASS 注解;如果只是做一些检查性的操作,比如 @Override 和 @SuppressWarnings,则可选用 SOURCE 注解。
源码下载
https://gitee.com/acc8226/javabijifanshe/blob/master/src/main/java/qy/likai/java/AnnotationTest.java
参考
-
Java 从小白到大牛-关东升 (作者)-第 27 章 注解(Annotation)-图灵社区
https://www.ituring.com.cn/book/tupubarticle/17723 -
自定义注解之运行时注解(RetentionPolicy.RUNTIME)Rukey7 的博客-CSDN 博客@retention(retentionpolicy.runtime)
https://blog.csdn.net/github_35180164/article/details/52118286