0%

Java注解

前言

注解是一种元数据形式,为编译器提供信息。

目录

一、注解是什么

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

二、如何使用

注解声明

使用@interface关键字来声明注解,需指定的元注解有两个 :@Target@Retention

1
2
3
4
5
6
//定义注解
@Target(ElementType.TYPE) //标记注解的作用元素类型范围
@Retention(RetentionPolicy.SOURCE) //标记注解的存储方式,注解保留级别
public @interface CustomAnnotation {
//Annotation element definitions
}

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

1
2
3
4
5
6
7
package java.lang.annotation; 
public interface Annotation {
boolean equals(Object obj);
int hashCode();
String toString();
Class<? extends Annotation> annotationType();
}
元注解

适用于其他注解的注解称为 meta-annotations (元注解)。在 java.lang.annotation 中定义了几种元注解类型。

@Retention指定标记注解的存储方式,注解保留级别

  • RetentionPolicy.SOURCE - 标记的注解仅保留在源码级别中,并被编译器忽略。
  • RetentionPolicy.CLASS - 标记的注解在编译时由编译器保留,但 Java 虚拟机(JVM)会忽略。
  • RetentionPolicy.RUNTIME - 标记的注解由 JVM 保留,因此运行时环境可以使用它。

注:SOURCE < CLASS < RUNTIME,即CLASS包含了SOURCE,RUNTIME包含SOURCE、CLASS。

@Target指定标记注解的作用元素访问,以限制可以应用注解的 Java 元素类型

  • ElementType.ANNOTATION_TYPE 可以应用于注解类型。
  • ElementType.CONSTRUCTOR 可以应用于构造函数。
  • ElementType.FIELD 可以应用于字段或属性。
  • ElementType.LOCAL_VARIABLE 可以应用于局部变量。
  • ElementType.METHOD 可以应用于方法级注解。
  • ElementType.PACKAGE 可以应用于包声明。
  • ElementType.PARAMETER 可以应用于方法的参数。
  • ElementType.TYPE 可以应用于类的任何元素。

@Documented表示无论何时使用指定的注解,都应使用 Javadoc 工具记录这些元素。

@Inherited表示注解类型可以从超类继承。(默认情况下不是这样。)当用户查询注解类型并且该类没有此类型的注解时,将查询类的超类以获取注解类型。此注解仅适用于类声明。

@Repeatable Java SE 8 中引入的注解表明标记的注解可以多次应用于同一声明或类型使用。

注解类型元素

在上文元注解中,允许在使用注解时传递参数,我们也能让自定义注解的主体包含 annotation type element (注解类型元素) 声明,它们看起来很像方法,可以定义可选的默认值。

1
2
3
4
5
6
7
8
//定义注解
@Target({ElementType.TYPE,ElementType.FIELD})
@Retention(RetentionPolicy.SOURCE)
public @interface AnnoName {
String value(); //无默认值
int age() default 1; //有默认值
//String[] array();
}
1
2
3
4
//使用注解
@AnnoName("帅") //如果只存在value元素需要传值的情况,则可以省略:元素名=
@AnnoName(value="帅",age = 2)
int i;

注:在使用注解时,如果定义的注解中的类型元素无默认值,则必须进行传值。

三、运用场景

由注解的三个不同保留级别可知,注解可用于源码、字节码、运行时三个场景,具体案例:

源码-> APT(注解处理器),在编译期获取注解和注解声明的类信息,用于生成辅助类。语法检查@IntDef

字节码->字节码增强(字节码插桩),在编译出Class后,通过注解区分是否修改Class数据以实现修改代码逻辑目的。

运行时->反射,通过反射动态获取注解及其元素,从而完成不同的逻辑判断。

IDE语法检查

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

1
2
3
4
5
6
7
 @Retention(SOURCE) //源码级别注解 
@Target({ANNOTATION_TYPE})
public @interface IntDef {
int[] value() default {};
boolean flag() default false;
boolean open() default false;
}

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

如果使用枚举能够实现为:

1
2
3
4
5
public enum Type{
TYPE_1,TYPE_2
}
public void test(Type type) {
}

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

现在为了进行内存优化,我们现在不再使用枚举,

1
2
3
4
public static final int TYPE_1 = 1; 
public static final int TYPE_2 = 2;
public void test(int type) {
}

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

1
2
3
4
5
6
7
8
9
10
public static final int TYPE_1 = 1; 
public static final int TYPE_2 = 2;

@IntDef({TYPE_1,TYPE_2})
@Target(ElementType.PARAMETER) //作用于参数的注解
@Retention(RetentionPolicy.SOURCE)
public @interface Type {
}
public void test(@Type int type) {
}

此时,我们再去调用 test 方法,如果传递的参数不是 TYPE_1 或者 TYPE_2 则会显示 Inspection 警告(编译不会报 错)。

总结

注解的保留级别有不同的应用场景:

  • 作用于源码级别的注解,可提供给IDE语法检查、APT等场景使用。

  • 定义为 CLASS 的注解,会保留在class文件中,但是会被虚拟机忽略(即无法在运行期反射获取注解)。应用场景为字节码操作。如:AspectJ、热修复Roubust中应用。

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