注解是JDK1.5版本开始引入的一个特性,用于对代码进行说明,可以对包、类、接口、字段、方法参数、局部变量等进行注解。
它是框架学习和设计者必须掌握的基础。

注解基础

注解是JDK1.5版本开始引入的一个特性,用于对代码进行说明,可以对包、类、接口、字段、方法参数、局部变量等进行注解。

它主要的作用有以下四方面:

  • 生成文档,通过代码里标识的元数据生成javadoc文档。
  • 编译检查,通过代码里标识的元数据让编译器在编译期间进行检查验证。
  • 编译时动态处理,编译时通过代码里标识的元数据动态处理,例如动态生成代码。
  • 运行时动态处理,运行时通过代码里标识的元数据动态处理,例如使用反射注入实例。

这么来说是比较抽象的,我们具体看下注解的常见分类:

  • Java自带的标准注解
    • @Override 标明重写某个方法,表示当前的方法定义将覆盖父类中的方法
    • @Deprecated 标明某个类或方法过时,表示代码被弃用,如果使用了被@Deprecated注解的代码则编译器将发出警告
    • @SuppressWarnings 标明要忽略的警告,表示关闭编译器警告信息
  • 元注解
    元注解是用于定义注解的注解。
    • @Retention 用于标明注解被保留的阶段
    • @Target 用于标明注解使用的范围
    • @Inherited 用于标明注解可继承
    • @Documented 用于标明是否生成javadoc文档
  • 自定义注解
    可以根据自己的需求定义注解,并可用元注解对自定义注解进行注解。

Java内置注解

@Override

1
2
3
4
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public @interface Override {
}

这个注解可以被用来修饰方法,并且它只在编译时有效,在编译后的class文件中便不再存在。
这个注解的作用就是告诉编译器被修饰的方法是重写的父类的中的相同签名的方法,编译器会对此做出检查,若发现父类中不存在这个方法或是存在的方法签名不同,则会报错。

@Deprecated

1
2
3
4
5
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(value={CONSTRUCTOR, FIELD, LOCAL_VARIABLE, METHOD, PACKAGE, PARAMETER, TYPE})
public @interface Deprecated {
}

从它的定义我们可以知道,它会被文档化,能够保留到运行时,能够修饰构造方法、属性、局部变量、方法、包、参数、类型。
这个注解的作用是告诉编译器被修饰的程序元素已被“废弃”,不再建议用户使用。

@SuppressWarnings

1
2
3
4
5
@Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE})
@Retention(RetentionPolicy.SOURCE)
public @interface SuppressWarnings {
String[] value();
}

它能够修饰的程序元素包括类型、属性、方法、参数、构造器、局部变量,只能存活在源码时,取值为String[]。
它的作用是告诉编译器忽略指定的警告信息。

元注解

上述内置注解的定义中使用了一些元注解(注解类型进行注解的注解类),在JDK 1.5中提供了4个标准的元注解:@Target@Retention@Documented@Inherited, 在JDK 1.8中提供了两个元注解 @Repeatable@Native

@Target

@Target注解的作用是:描述注解的使用范围(即:被修饰的注解可以用在什么地方)。

@Target注解用来说明那些被它所注解的注解类可修饰的对象范围:packages、types(类、接口、枚举、注解类)、类成员(方法、构造方法、成员变量、枚举值)、方法参数和本地变量(如循环变量、catch参数)
在定义注解类时使用@Target能够更加清晰的知道它能够被用来修饰哪些对象,它的取值范围定义在ElementType 枚举中。

1
2
3
4
5
6
7
8
9
10
11
12
public enum ElementType {
TYPE, // 类、接口、枚举类
FIELD, // 成员变量(包括:枚举常量)
METHOD, // 成员方法
PARAMETER, // 方法参数
CONSTRUCTOR, // 构造方法
LOCAL_VARIABLE, // 局部变量
ANNOTATION_TYPE, // 注解类
PACKAGE, // 可用于修饰:包
TYPE_PARAMETER, // 类型参数,JDK 1.8 新增
TYPE_USE // 使用类型的任何地方,JDK 1.8 新增
}

@Retention

@Reteniton注解用来限定那些被它所注解的注解类在注解到其他类上以后,可被保留到何时。

一共有三种策略,定义在RetentionPolicy枚举中。

1
2
3
4
5
public enum RetentionPolicy {
SOURCE, // 源文件保留
CLASS, // 编译期保留,默认值
RUNTIME // 运行期保留,可通过反射去获取注解信息
}
  1. 为了验证应用了这三种策略的注解类有何区别,分别使用三种策略各定义一个注解类做测试。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    @Retention(RetentionPolicy.SOURCE)
    public @interface SourcePolicy {
    }

    @Retention(RetentionPolicy.CLASS)
    public @interface ClassPolicy {
    }

    @Retention(RetentionPolicy.RUNTIME)
    public @interface RuntimePolicy {
    }
  2. 用定义好的三个注解类分别去注解一个方法。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    public class RetentionTest {
    @SourcePolicy
    public void sourcePolicy() {
    }
    @ClassPolicy
    public void classPolicy() {
    }
    @RuntimePolicy
    public void runtimePolicy() {
    }
    }
  3. 通过执行javap -verbose RetentionTest命令获取到的 RetentionTest 的 class 字节码内容如下:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    {
    public retention.RetentionTest();
    flags: ACC_PUBLIC
    Code:
    stack=1, locals=1, args_size=1
    0: aload_0
    1: invokespecial #1 // Method java/lang/Object."<init>":()V
    4: return
    LineNumberTable:
    line 3: 0

    public void sourcePolicy();
    flags: ACC_PUBLIC
    Code:
    stack=0, locals=1, args_size=1
    0: return
    LineNumberTable:
    line 7: 0

    public void classPolicy();
    flags: ACC_PUBLIC
    Code:
    stack=0, locals=1, args_size=1
    0: return
    LineNumberTable:
    line 11: 0
    RuntimeInvisibleAnnotations:
    0: #11()

    public void runtimePolicy();
    flags: ACC_PUBLIC
    Code:
    stack=0, locals=1, args_size=1
    0: return
    LineNumberTable:
    line 15: 0
    RuntimeVisibleAnnotations:
    0: #14()
    }
    • 编译器并没有记录下 sourcePolicy() 方法的注解信息;
    • 编译器分别使用了 RuntimeInvisibleAnnotationsRuntimeVisibleAnnotations 属性去记录了classPolicy()方法runtimePolicy()方法的注解信息。

@Documented

@Documented注解的作用是:描述在使用 javadoc 工具为类生成帮助文档时是否要保留其注解信息。

@Inherited

@Inherited注解的作用:被它修饰的Annotation将具有继承性。
如果某个类使用了被@Inherited修饰的Annotation,则其子类将自动具有该注解。

注解与反射接口

自定义注解

深入理解注解

注解支持继承吗
注解是不支持继承的。

不能使用关键字extends来继承某个@interface,但注解在编译后,编译器会自动继承java.lang.annotation.Annotation接口。
虽然反编译后发现注解继承了Annotation接口,请记住,即使Java的接口可以实现多继承,但定义注解时依然无法使用extends关键字继承@interface。

区别于注解的继承,被注解的子类继承父类注解可以用@Inherited,如果某个类使用了被@Inherited修饰的Annotation,则其子类将自动具有该注解。

注解实现的原理
https://blog.csdn.net/qq_20009015/article/details/106038023
https://www.race604.com/annotation-processing/