Java 注解

1,294 阅读6分钟

之前写过一遍Java注解 annotation文章,但是当时对注解的运用理解不多,有些知识点也一知半解。最近看了Retrofit源码,对注解的使用有了进一步的理解。此篇文章算是对之前知识的复习,以及对注解使用的补充说明。

注解是用来做什么的

注解就像是一个个标签,用来标记你的代码,可以理解成是一种应用于类、方法、参数、变量、构造器和包的特殊修饰符。


注解的作用我理解下来,一般有如下三种应用


1. 提供信息给编译器:编译器可以通过注解来探测错误和警告信息

这种应用,比较典型的例子就是@Override注解,用来标记方法重写,源码编译阶段该注解会被丢弃。

2. 编译阶段处理:软件工具可以利用注解信息来自动生成代码,HTML文档或者做其他相应处理

例如:butterknife框架里面的注解,就使用了编译生成代码(APT)技术。

3. 运行时的处理: 某些注解可以在程序运行时接收代码的提取,为程序提供逻辑判断依据或者逻辑处理依据

例如:Retrofit框架自定义的@Get@Post注解,在处理请求逻辑时解析这些注解,实现相关的请求逻辑。

如何声明注解


格式

@元注解
public @interface 注解名称 {
    属性列表;//可以没有
}

本质

注解和类、接口、枚举是同一级别的。

注解的本质是一个继承java.lang.annotation.Annotation接口的接口

属性

  1. 属性声明:

    注解的属性,也叫成员变量

    Q:既然注解本质是个接口,我们都知道接口是不能定义成员变量的,那么注解的属性是什么?

    @Documented
    @Target(METHOD)
    @Retention(RUNTIME)
    public @interface GET {
      String value() default "";
    }
    

    Retrofit@Get为例,我们可以看到注解的属性实际对应的是接口方法。在注解里面我们将类似String value()这种称为属性。显然对'属性'赋值听起来比对方法赋值好接受多了。

    A:注解的属性在注解的定义中以无参方法的形式声明,方法名即为属性名,返回值为该属性的类型。

    注解的属性类型,必须为如下几个类型:

    8种基本类型(byte,boolean,char,short,int,long,float,double) 和String,Enum,Class,annotation类型,以及这些类型的数组

  2. 属性赋值

    属性赋值以属性名=xxx的形式赋值,多个属性以,号隔开。

    注解的属性可以通过default关键字指定默认值,如果指定了默认值则可以在使用时不赋值直接使用默认值,否则必须显式的赋值

    如果注解仅有一个属性且名为value时,赋值时可以省略属性名

    以上面的@Get注解为例:

    @GET(value = "1111")
    void testAssign2();
    
    //或者省略value
    @GET("1111")
    void testAssign1();
    

元注解

关于注解的声明还有一个非常重要的概念就是元注解。

元注解是一种基本注解,可以用来注解其他注解;可以看成一种特殊的修饰符,用来解释说明注解。

常用的元注解有如下几种:

  1. @Retention

    说明: 用来标识注解的存活周期

    取值:

    public enum RetentionPolicy {
     /**
      * 注解仅在源码阶段保留,编译时会被丢弃
      */
     SOURCE,
    
     /**
      * 注解会被保留到Class文件中,但是运行时会被JVM丢弃
      */
     CLASS,
    
     /**
      * 注解会被保留到Class文件中,运行时也会保留
      */
     RUNTIME
     
     }
    
  2. @Documented

    说明: 用来表示注解可以作为公共API,可以被例如javadoc之类的工具提取成文档。@Documented仅用来标记,没有属性。

  3. @Target

    说明: 用来限定当前注解的应用场景(类,方法等等),不使用则默认不限制。

    @Target的属性类型为数组,因此同时赋值多个值,数组类型数组赋值方式:{}内,多个值使用逗号分隔,如果只赋值一个值可以省略{}

    示例:

     @Target({METHOD, PARAMETER, FIELD})
     public @interface FontRes {
     }
    

    取值:

    public enum ElementType {
     /** 类、接口(包括注解)、枚举 */
     TYPE,
    
     /** 属性 (包括枚举常量) */
     FIELD,
    
     /** 方法 */
     METHOD,
    
     /** 方法参数 */
     PARAMETER,
    
     /** 构造函数 */
     CONSTRUCTOR,
    
     /** 局部变量 */
     LOCAL_VARIABLE,
    
     /** 注解 */
     ANNOTATION_TYPE,
    
     /** 包 */
     PACKAGE,
    
     /**
      * 用在类型参数声明的地方,java8新增
      */
     TYPE_PARAMETER,
    
     /**
      * 用在类型使用的地方,java8新增
      */
     TYPE_USE
     }
    
  4. @Inherited

    说明: 表示允许子类继承当前注解;一个超类被当前注解修饰,则其子类也具有此注解。@Interited没有属性值。

    注意: 类不能从它实现的接口继承注解,方法也不能从它所重载的方法继承注解

  5. @Repeatble

    说明: 用来标记当前注解可以重复,java1.8新增特性。在同一场景下需要多次使用同一注解时,可以通过@Repeatable实现

    使用示例:

     /**
     自定义容器注解Roles(用来存放其他注解,里面必须要有一个 value 的属性,
     属性类型是一个被 @Repeatable 注解过的注解数组)
     */
     @Target(ElementType.TYPE)
     @Retention(RetentionPolicy.RUNTIME)
     public @interface Roles {
     Role[] value();
     }
    
     /**
     自定义注解Role,使用@Repeatable注解,这样Role注解就可以多次使用
     */
     @Repeatable(Roles.class)
     public @interface Role {
     String role() default "";
     }
    
     //使用示例
     @Role(role="husband")
     @Role(role="father")
     @Role(role="son")
     public class Person {
     }
    

如何使用注解

这里所说的使用,不仅仅是简单含义的使用,还包括注解的读取,赋予注解真正意义上的作用

注解分为:

  • JDK内置注解:例如@Override@Deprecated等等,这类注解编译器会读取处理,实现它们实际代表的功能

  • 第三方框架提供的注解:例如Retrofit框架的@Get@Post等注解,这类注解框架会读取处理,赋予注解实际的意义

  • 自定义注解:这类注解就需要我们自己读取,来赋予它们实际的意义。不然仅仅是声明注解和在对应场景(方法,参数等地方)使用该注解,声明和使用等注解是没有任何意义的。

关于自定义的注解的提取和使用,一般我们有两种方式

  1. 编译时扫描和处理注解(即APT,Annotation Processing Tool),具体可先参考Java注解应用

  2. 运行时扫描和处理注解(通过java反射相关API)。这个可以参考Retrofit框架对于运行时注解的运用,后面考虑写个这方面的使用示例,敬请期待...