Appearance
Java 注解
注解(Annotation),也叫元数据。一种代码级别的说明。它是JDK1.5引入的一个特性,与类、接口、枚举是在同一个层次。它可以声明在包、类、接口、字段、方法、局部变量、方法参数等的前面,用来对这些元素进行说明。
1. 注解分类
- Java自带的标准注解,包括@Override、@Deprecated、@SuppressWarnings等。
- 元注解,元注解是用于定义注解的注解,包括@Retention、@Target、@Inherited、@Documented,@Retention等。
- 自定义注解,可以根据自己的需求定义注解,并可用元注解对自定义注解进行注解。
1.1 JDK自带的标准注解
1.1.1 @Override
@Override标注在成员方法上,用于标识当前方法是重写父类(父接口)方法,编译器在对该方法进行编译时会检查是否符合重写规则,如果不符合,编译报错。 例如:
java
interface Eat{
void eat();
}
public class Dog implements Eat{
@Override
public void eat() {
System.out.println("小狗正在吃饭");
}
}1.1.2 @Deprecated
@Deprecated用于标记类、成员变量、成员方法或者构造方法等元素上,表明这些元素已经过时了。如果开发者调用了被标记为过时的方法,编译器会在编译期进行警告。并且在IDEA中,过时的元素也会以删除线表示。
例如:

1.1.3 @SuppressWarnings
@SuppressWarnings用于标记类、成员变量、成员方法、方法参数、构造方法和局部变量,作用是抑制警告(即让警告消失)。
例如:

可以看到,虽然Eat接口被标记为过时的,但是由于抑制了过时警告,所以实现Eat接口时,Eat接口也没有以删除线表示。
完整的抑制列表如下:
- all 抑制所有警告
- boxing 抑制与装箱/拆箱操作相关的警告
- cast 抑制与强制转换类型操作相关的警告
- dep-ann 抑制与不推荐使用的注释相关的警告
- deprecation 抑制与不推荐使用的内容相关的警告
- fallthrough 抑制与 switch 语句中缺少的 break 相关的警告
- finally 抑制与未返回的 finally 块相关的警告
- hiding 抑制与隐藏变量的局部变量相关的警告
- incomplete-switch 抑制与 switch 语句(枚举 case)中缺少的条目相关的警告
- javadoc 抑制与 Javadoc 警告相关的警告
- nls 抑制与非 nls 字符串文字相关的警告
- null 抑制与 null 分析相关的警告
- rawtypes 抑制与原始类型的使用相关的警告
- resource 抑制与“可关闭”类型的资源的使用相关的警告
- restriction 抑制与使用不推荐的引用或禁止的引用相关的警告
- serial 抑制由于可序列化类缺少 serialVersionUID 字段而发出的警告
- static-access 抑制与不正确的静态访问相关的警告
- static-method 抑制与可能声明为静态方法的方法相关的警告
- super 抑制与没有超级调用的情况下覆盖方法相关的警告
- synthetic-access 抑制与内部类中未优化的访问相关的警告
- sync-override 抑制因覆盖同步方法时丢失同步产生的警告
- unchecked 抑制与未检查的操作相关的警告
- unqualified-field-access 抑制与未限定的字段访问相关的警告
- unused 抑制与未使用代码和死代码相关的警告
参考链接:https://www.ibm.com/docs/zh/radfws/9.6.1?topic=code-excluding-warnings
1.2 元注解
1.2.1 @Target
@Target的作用是描述注解的使用范围(即:被修饰的注解可以用在什么地方) 。
它的取值定义在ElementType中:
java
public enum ElementType {
TYPE, // 类、接口、枚举类
FIELD, // 成员变量(包括:枚举常量)
METHOD, // 成员方法
PARAMETER, // 方法参数
CONSTRUCTOR, // 构造方法
LOCAL_VARIABLE, // 局部变量
ANNOTATION_TYPE, // 注解类
PACKAGE, // 可用于修饰:包
TYPE_PARAMETER, // 类型参数,JDK 1.8 新增
TYPE_USE // 使用类型的任何地方,JDK 1.8 新增
}例如@Deprecated注解的使用范围如下:
java
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(value={CONSTRUCTOR, FIELD, LOCAL_VARIABLE, METHOD, PACKAGE, PARAMETER, TYPE})
public @interface Deprecated {
}1.2.2 @Retention
@Retention的作用是描述注解的生存周期,取值在RetentionPolicy中指定:
java
public enum RetentionPolicy {
SOURCE, // 源文件保留
CLASS, // 编译期保留,默认值
RUNTIME // 运行期保留,可通过反射去获取注解信息
}一个java文件,从源文件到程序运行,会经过如下周期:
源文件阶段 - > class类对象阶段 - > Runtime运行时阶段- 指定生存周期是
SOURCE,那么该注解指挥在源文件中存在,在编译时会忽略,即在class字节码文件中不存在该注解; - 指定生存周期是
CLASS,那么该注解在源文件和class字节码文件中都存在,但是运行时加载类进内存时,不会加载该注解; - 指定生存周期是
RUNTIME,即该注解会保存在源文件和class字节码文件中,并且运行时加载进内存,即可以使用反射获取到该注解;
例如,指定@Author的生存周期是CLASS,那么使用反射获取不到该注解:
java
@Retention(RetentionPolicy.CLASS)
public @interface Author {
String value();
}
@Author("水木子")
public class Demo {
public static void main(String[] args) {
Class<Demo> clazz = Demo.class;
Author annotation = clazz.getAnnotation(Author.class);
System.out.println(annotation); // null
}
}但是在字节码文件中,是存在该注解的:

1.2.3 @Inherited
@Inherited的作用是使被修饰的注解具有继承性,即如果某个类使用了被@Inherited修饰的注解,则其子类将自动具有该注解。
例如:
java
@Inherited
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface Version {
String value();
}
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface Author {
String value();
}java
@Version("1.0")
@Author("smz")
public class Parent {
}java
public class Child extends Parent{
}java
public static void main(String[] args) {
Class<Child> clazz = Child.class;
Version version = clazz.getAnnotation(Version.class);
System.out.println(version); // @annotation.t3.Version(value=1.0)
Author author = clazz.getAnnotation(Author.class);
System.out.println(author); // null
}可以看到,即使子类没有注解@Version,但是从父类继承到了该注解,所以反射也获取到了。
1.2.4 @Documented
@Documented的作用是使得该注解出现在JavaDoc文档中,例如:
java
@Documented
@Retention(RetentionPolicy.RUNTIME)
public @interface Author {
String value();
}
@Author("smz")
public class Demo {
}生成JavaDoc文档后,该注解会保留:

另外,在IDEA中生成JavaDoc的方法如下:

1.2.5 @Repeatable
@Repeatable 是 Java 8 引入的,用于指示某个注解可以在同一个目标上重复使用。在没有 @Repeatable 的情况下,同一个注解在同一个目标上只能使用一次。
如果在Java 8 之前,如果一个注解要在一个元素上反复使用,则可以用如下方式实现:
java
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface Role {
String value();
}
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface Roles {
Role[] value();
}java
public class GoodsController {
@Roles({@Role("admin"), @Role("maintainer")})
public void deleteGoods(String id){
// 删除商品
}
}使用了@Repetable注解后,我们可以这样定义:
java
@Repeatable(Roles.class)
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface Role {
String value();
}
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface Roles {
Role[] value();
}注意,@Repeatable 需要指定一个容器注解类(Container Annotation Class),用于存储重复的注解实例,容器注解中必须包含一个名为 value 的方法,且返回类型为可重复注解的数组。
java
public class GoodsController {
@Role("admin")
@Role("maintainer")
public void deleteGoods(String id){
// 删除商品
}
}在反射中,我们可以使用getAnnotationsByType()获取重复注解:
java
public static void main(String[] args) throws NoSuchMethodException {
Class<GoodsController> clazz = GoodsController.class;
Method method = clazz.getMethod("deleteGoods", String.class);
Role[] roles = method.getAnnotationsByType(Role.class);
for (Role role : roles) {
System.out.println(role);
}
}
//@annotation.t4.Role(value=admin)
//@annotation.t4.Role(value=maintainer)1.2.6 @Native
@Native 是 Java 8 引入的一个注解,用于标记常量字段(static final 字段),表示该字段的值可能被本地代码(Native Code)引用。它的主要作用是提供一种提示,告诉开发者和工具,这个字段可能会被 JNI(Java Native Interface)访问。不常用,略过。
1.3 自定义注解
自定义注解格式如下:
java
public @Interface 注解名 {
属性列表(可为空)
}例如:
java
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface Author {
String value();
}2. 通过反射解析注解
这里演示包注解的使用:
首先定义一个包注解:
java
@Target(value = {ElementType.PACKAGE})
@Retention(RetentionPolicy.RUNTIME)
public @interface PackageInfo {
String author();
String version();
}然后创建package-info.java(并不是Java源码文件,是文本文件)文件:
java
@PackageInfo(version = "1.0", author = "smz")
package annotation.t1;
import annotation.t1.PackageInfo;之后就可以使用反射获取到注解内容:
java
public static void main(String[] args) {
Package p = Package.getPackage("annotation.t1");
System.out.println(p.getName()); // annotation.t1
if(p.isAnnotationPresent(PackageInfo.class)){
PackageInfo annotation = p.getAnnotation(PackageInfo.class);
System.out.println(annotation.version()); // 1.0
System.out.println(annotation.author()); // smz
}
}参考资料
[1] https://www.cnblogs.com/ziph/p/13056092.html
[2] https://pdai.tech/md/java/basic/java-basic-x-annotation.html