注解的定义

Java 注解(Annotation)又称 Java 标注,是 JDK5.0 引入的一种注释机制。 注解是元数据的一种形式,提供有关于程序但不属于程序本身的数据。注解对它们注解的代码的操作没有直接影响。

Annotation 接口类

Java中所有的注解,默认实现 Annotation 接口:

package java.lang.annotation;

public interface Annotation {

boolean equals(Object obj);

int hashCode();

String toString();

Class<? extends Annotation> annotationType();

}

如何自定义注解

● 注解通过 @interface关键字进行定义。
public @interface Test {
}

它的形式跟接口很类似,不过前面多了一个 @ 符号。上面的代码就创建了一个名字为 Test 的注解。
你可以简单理解为创建了一张名字为 Test的标签。
● 使用注解
@Test
public class TestAnnotation {
}

创建一个类 TestAnnotation,然后在类定义的地方加上 @Test就可以用 Test注解这个类了
你可以简单理解为将 Test 这张标签贴到 TestAnnotation这个类上面。

元注解

元注解(meta-annotation)是可以注解到注解上的注解,或者说元注解是一种基本注解,但是它能够应用到其它的注解上面。

如果难于理解的话,你可以这样理解。元注解也是一张标签,但是它是一张特殊的标签,它的作用和目的就是给其他普通的标签进行解释说明的。

元标签有 @Retention、@Documented、@Target、@Inherited、@Repeatable 5 种。

我们在定义自定义注解时一般使用@Retention、@Target,其余元注解较少使用。

@Retention

Retention 的英文意为保留期的意思。当 @Retention 应用到一个注解上的时候,它解释说明了这个注解的的存活时间。
它的取值如下:

  1. RetentionPolicy.SOURCE 注解只在源码阶段保留,在编译器进行编译时它将被丢弃忽视。
  2. RetentionPolicy.CLASS 注解只被保留到编译进行的时候,它并不会被加载到 JVM 中。
  3. RetentionPolicy.RUNTIME 注解可以保留到程序运行的时候,它会被加载进入到 JVM 中,所以在程序运行时可以获取到它们

下面是RetentionPolicy枚举类源码:

package java.lang.annotation;

public enum RetentionPolicy {
/**
* Annotations are to be discarded by the compiler.
*/
SOURCE,

/**
 * Annotations are to be recorded in the class file by the compiler
 * but need not be retained by the VM at run time.  This is the default
 * behavior.
 */
CLASS,

/**
 * Annotations are to be recorded in the class file by the compiler and
 * retained by the VM at run time, so they may be read reflectively.
 *
 * @see java.lang.reflect.AnnotatedElement
 */
RUNTIME

}

【示例】

//@Target(ElementType.TYPE) 只能在类上标记该注解
@Target({ElementType.TYPE,ElementType.FIELD}) // 允许在类与类属性上标记该注解
@Retention(RetentionPolicy.SOURCE) //注解保留在源码中
public @interface MyAnnotation{
}

@Retention 三个值中 SOURCE < CLASS < RUNTIME,即CLASS包含了SOURCE,RUNTIME包含SOURCE、CLASS。下文会介绍他们不同的应用场景。

@Target

注解标记另一个注解,以限制可以应用注解的 Java 元素类型。目标注解指定以下元素类型之一作为其值,下面是ElementType枚举类:

package java.lang.annotation;

public enum ElementType {
/** Class, interface (including annotation type), or enum declaration */
// 可以应用于类的任何元素。
TYPE,

/** Field declaration (includes enum constants) */
FIELD,

/** Method declaration */
METHOD,

/** Formal parameter declaration */
PARAMETER,

/** Constructor declaration */
CONSTRUCTOR,

/** Local variable declaration */
LOCAL_VARIABLE,

/** Annotation type declaration */
ANNOTATION_TYPE,

/** Package declaration */
PACKAGE,

/**
 * Type parameter declaration
 *
 * @since 1.8
 */
TYPE_PARAMETER,

/**
 * Use of a type
 *
 * @since 1.8
 */
TYPE_USE

}

@Documented

顾名思义,这个元注解肯定是和文档有关。它的作用是能够将注解中的元素包含到 Javadoc 中去。ElementType.TYPE 可以给一个类型进行注解,比如类、接口、枚举

@Inherited

Inherited 是继承的意思,但是它并不是说注解本身可以继承,而是说如果一个超类被 @Inherited 注解过的注解进行注解的话,那么如果它的子类没有被任何注解应用的话,那么这个子类就继承了超类的注解。

@Repeatable

Repeatable 自然是可重复的意思。@Repeatable 是 Java 1.8 才加进来的,所以算是一个新的特性。

什么样的注解会多次应用呢?通常是注解的值可以同时取多个。

注解的属性

  1. 注解的属性也叫做成员变量。注解只有成员变量,没有方法。
  2. 需要注意的是,在注解中定义属性时它的类型必须是 8 种基本数据类型外加类、接口、注解及它们的数组
  3. 注解中属性可以有默认值,默认值需要用 default 关键值指定

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Test{
int id() default -1; // 有默认值
String msg(); // 无默认值
}

  1. 上面代码定义了 TestAnnotation 这个注解中拥有 id 和 msg 两个属性。在使用的时候,我们应该给它们进行赋值。
  2. 赋值的方式是在注解的括号内以 value=”” 形式,多个属性之前用,隔开。
  3. 在使用注解时,如果定义的注解中的类型元素无默认值,则必须进行传值。

@Test(id=1,msg=”hello annotation”)
public class TestAnnotation {

}

注解的提取(反射)

注解通过反射获取。首先可以通过 Class 对象的 isAnnotationPresent() 方法判断它是否应用了某个注解。

public boolean isAnnotationPresent(Class<? extends Annotation> annotationClass ) {}

然后通过 getAnnotation() 方法来获取 Annotation 对象。

public A getAnnotation(Class annotationClass) {}

或者是 getAnnotations() 方法。

public Annotation[] getAnnotations() {}

前一种方法返回指定类型的注解,后一种方法返回注解到这个元素上的所有注解。

如果获取到的 Annotation 如果不为 null,则就可以调用它们的属性方法了。比如

@Test()
public class TestDemo{

public static void main(String[] args) {
boolean hasAnnotation =TestDemo.class.isAnnotationPresent(Test.class);
if (hasAnnotation) {
TestAnnotation testAnnotation =TestDemo.class.getAnnotation(Test.class);
System.out.println(“id:”+testAnnotation.id());
System.out.println(“msg:”+testAnnotation.msg());
}
}
}

注解的多态

// TODO
在注解的基础上,再注解。

注解的使用场景

按照 @Retention 元注解定义的注解存储方式,注解可以被在三种场景下使用:

  1. 注解为SOURCE级别

作用于源码级别的注解,可提供以下场景使用:

  1. IDE语法检查
  2. APT 注解处理工具。
  3. 其它…

在类中使用 SOURCE 级别的注解,其编译之后的class中会被丢弃。

1.1. IDE语法检查

在Android开发中, support-annotations 与 androidx.annotation 中均有提供 @IntDef 注解,此注解的定义如下:

package androidx.annotation;

import static java.lang.annotation.ElementType.ANNOTATION_TYPE;
import static java.lang.annotation.RetentionPolicy.SOURCE;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;

@Retention(SOURCE) // 源码级别注解
@Target({ANNOTATION_TYPE})
public @interface IntDef {
/** Defines the allowed constants for this element */
int[] value() default {};

/** Defines whether the constants can be used as a flag, or just as an enum (the default) */
boolean flag() default false;

/**
 * Whether any other values are allowed. Normally this is
 * not the case, but this allows you to specify a set of
 * expected constants, which helps code completion in the IDE
 * and documentation generation and so on, but without
 * flagging compilation warnings if other values are specified.
 */
boolean open() default false;

}

Java中Enum(枚举)的实质是特殊单例的静态成员变量,在运行期所有枚举类作为单例,全部加载到内存中。比常量多5到10倍的内存占用。

此注解的意义在于能够取代枚举,实现如方法入参限制。

如:我们定义方法 test ,此方法接收参数 teacher 需要在:Lance、Alvin中选择一个。如果使用枚举能够实现为:

public enum Teacher{
LANCE,ALVIN
}

public void test(Teacher teacher) {

}

而现在为了进行内存优化,我们现在不再使用枚举,则方法定义为:

public static final int LANCE = 1;
public static final int ALVIN = 2;

public void test(int teacher) {

}

然而此时,调用 test 方法由于采用基本数据类型int,将无法进行类型限定。此时使用@IntDef增加自定义注解:

public class A {

public static final int A = 1;
public static final int B = 2;

@IntDef(value = {A, B}) //限定为A,B
@Target(ElementType.PARAMETER) //作用于参数的注解
@Retention(RetentionPolicy.SOURCE) //源码级别注解
public @interface Teacher {

}

public static void test(@Teacher int teacher) {

}

public static void main(String[] args) {
    test(A);  //这里的实参必须是 A 或者 B , 如果填 C , IDE会报错。
}

}

1.2. APT注解处理器

APT全称为:”Anotation Processor Tools”,意为注解处理器。顾名思义,其用于处理注解。编写好的Java源文件,需要经过 javac 的编译,翻译为虚拟机能够加载解析的字节码Class文件。注解处理器是 javac 自带的一个工具,用来在编译时期扫描处理注解信息。你可以为某些注解注册自己的注解处理器。 注册的注解处理器由 javac 调起,并将注解信息传递给注解处理器进行处理。

注解处理器是对注解应用最为广泛的场景。在Glide、EventBus3、Butterknifer、Tinker、ARouter等等,常用框架中都有注解处理器的身影。但是你可能会发现,这些框架中对注解的定义并不是 SOURCE 级别,更多的是 CLASS 级别,别忘了:CLASS包含了SOURCE,RUNTIME包含SOURCE、CLASS。

关于注解处理器的实现,请看《APT 注解处理工具》。

  1. 注解为CLASS级别

定义为 CLASS 的注解,会保留在class文件中,但是会被虚拟机忽略(即无法在运行期反射获取注解)。

所以此种注解的应用场景为字节码操作。如:AspectJ、热修复Roubust中应用此场景。

所谓字节码操作即为,直接修改字节码Class文件以达到修改代码执行逻辑的目的。在程序中有多处需要进行,是否登录的判断。

相关实现请看《ASM》。

  1. 注解为RUNTIME级别

注解保留至运行期,意味着我们能够在运行期间结合反射技术获取注解中的所有信息。

相关实现请看Java 反射。