Java 注解
注解的本质就是给载体打一个Tag,我们查找到这个tag后就可以对我们打Tag的载体进行一些特殊处理
Java 注解(Annotation)又称 Java 标注,是 JDK5.0 引入的一种注释机制。 注解是元数据的一种形式,提供有关 于程序但不属于程序本身的数据。注解对它们注解的代码的操作没有直接影响。
中所有的注解,默认实现
Annotation
接口:
package java.lang.annotation;
public interface Annotation {
boolean equals(Object obj);
int hashCode();
String toString();
Class<? extends Annotation> annotationType();
}
与声明一个"Class"不同的是,注解的声明使用 @interface 关键字。一个注解的声明如下:
public @interface FirstAnnotation {
String name();
int age();
}
元注解
在定义注解时,注解类也能够使用其他的注解声明。对注解类型进行注解的注解类,我们称之为 metaannotation(元注解)。一般的,我们在定义自定义注解时,需要指定的元注解有两个 :
@Documented
与
@Inherited
元注解,前者用于被
javadoc
工具提取成文档,
注解
,@Repeatable是jdk8中新增的注解在没有@Repeatable元注解的注解中,在同一个地方使用相同的注解会报错,有了此元注解注,既可以在同一个地方使用相同的注解。
Java
元素类型。目标注解指定以下元素类型之一作为其值:
可以应用于注解类型。
可以应用于构造函数。
可以应用于字段或属性。
可以应用于局部变量。
可以应用于方法级注解。
可以应用于包声明。
可以应用于方法的参数。
可以应用于类的任何元素。
可以应用于类型变量的声明语句中(eg:泛型声明)
@Retention
注解指定标记注解的存储方式:
-
标记的注解仅保留在源级别中,并被编译器忽略。
-
标记的注解在编译时由编译器保留,但
Java
虚拟机
(JVM)
会忽略。
-
标记的注解由
JVM
保留,因此运行时环境可以使用它。
三个值中
SOURCE < CLASS < RUNTIME
,即
CLASS
包含了
SOURCE
,
RUNTIME
包含
SOURCE
、 CLASS。下文会介绍他们不同的应用场景。
@Retention(RetentionPolicy.SOURCE) //只保留在java源码中
@Target({ElementType.FIELD}) //只能作用于属性
public @interface FirstAnnotation {
String name(); //无默认值
int age() default 18; // 有默认值
}
注意:在使用注解时,如果定义的注解中的类型元素无默认值,则必须进行传值。
public class AnnotationTest {
@FirstAnnotation("那路多") //如果只存在value元素需要传值的情况,则可以省略:value =
Student student; //注意只有命名为value才能省略 value = 直接写
@FirstAnnotation(value = "萨斯给",age = 18)
Student student1;
}
@Retention
元注解定义的注解存储方式,注解可以被在三种场景下使用:
在把java文件编译成Class文件后source级别的注解就会消失:
可以看到编译后source级别的注解消失了
可以用于 IDE检查和APT等场景使用
Android
开发中,
support
-
annotations
与
androidx.annotation
中均有提供
@IntDef
、
@DrawableResId
等注 解,此注解的定义如下:
@Retention(SOURCE) //源码级别注解
@Target({ANNOTATION_TYPE})
public @interface IntDef {
int[] value() default {};
boolean flag() default false;
boolean open() default false;
}
中
Enum(
枚举
)
的实质是特殊单例的静态成员变量,在运行期所有枚举类作为单例,全部加载到内存中。 比常量多5
到
10
倍的内存占用。
@IntDef(value = {1,2}) // 限定只能传 1 和 2
@Retention(RetentionPolicy.SOURCE) //只保留在java源码中
@Target({ElementType.PARAMETER}) //只能作用于方法参数之上
public @interface IntDefAnnotationTest {
int value();
}
public class AnnotationTest {
public void test(@IntDefAnnotationTest(value = 1) int a){
System.out.println(a);
}
}
如果传1或者2编译器可以通过
会显示
Inspection
警告

IDE报警了但是还是能编译通过和运行
可以修改此类语法检查级别:
可以看到AS没有再报错,本身IDEA/AS 就是由Java开发的,Lint工具实现了对Java语法的检查,借助注解能对被注解的特定语法进行额外检查。
全称为:
"Anotation Processor Tools"
,意为注解处理器。顾名思义,其用于处理注解。编写好的
Java
源文 件,需要经过 javac
的编译,翻译为虚拟机能够加载解析的字节码
Class
文件。注解处理器是
javac
自带的一个工 具,用来在编译时期扫描处理注解信息。你可以为某些注解注册自己的注解处理器。 注册的注解处理器由 javac调起,并将注解信息传递给注解处理器进行处理。
Glide
、
EventBus3
、
Butterknifer
、
Tinker
、
ARouter
等等常用 框架中都有注解处理器的身影。但是你可能会发现,这些框架中对注解的定义并不是 SOURCE
级别,更多的是 CLASS
级别,别忘了:
CLASS
包含了
SOURCE
,
RUNTIME
包含
SOURCE
、
CLASS
。
字节码增强(插桩)技术
CLASS
的注解,会保留在
class
文件中,但是会被虚拟机忽略
(
即无法在运行期反射获取注解
)
。此时完全符合 此种注解的应用场景为字节码操作。如:AspectJ
、热修复
Roubust
中应用此场景。 所谓字节码操作即为,直接修改字节码Class
文件以达到修改代码执行逻辑的目的。在程序中有多处需要进行是否登录的判断。
if
-
else
的判断,也许存在十个判断点,则需要在每个判断点加 入此项判断。此时,我们可以借助AOP(
面向切面
)
编程思想,将程序中所有功能点划分为:
需要登录
与
无需登录两种类型,即两个切面。对于切面的区分即可采用注解。
//Java源码
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.CLASS)
public @interface Login {
}
@Login
public void jumpA() {
startActivity(new Intent(this, AActivity.class));
}
public void jumpB() {
startActivity(new Intent(this, BActivity.class));
}
jumpA
方法需要具备登录身份。而
Login
注解的定义被设置为
CLASS
。因此我们能够在该类所编 译的字节码中获得到方法注解 Login
。在操作字节码时,就能够根据方法是否具备该注解来修改
class
中该方法的内容加入 if
-
else
的代码段:
//Class字节码
@Login
public void jumpA() {
if (this.isLogin) {
this.startActivity(new Intent(this, LoginActivity.class));
} else {
this.startActivity(new Intent(this, AActivity.class));
}
}
public void jumpB() {
startActivity(new Intent(this, BActivity.class));
}
(
参数
)
,结合参数能实现更为丰富的场景,如:运行期权限判定等。
实现:用反射技术和RUNTIME级别注解,实现ButterKnife功能
@Inherited
作用:如果一个类用上了@Inherited修饰的注解,那么其子类也会继承这个注解
注意是作用在类上的注解切必须是继承关系。
注意:
- 接口用上个@Inherited修饰的注解,其实现类不会继承这个注解
- 父类的方法用了@Inherited修饰的注解,子类也不会继承这个注解
例:
@Inherited
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE,ElementType.FIELD})
public @interface FirstAnnotation {
String value();
int age() default 18;
}
@FirstAnnotation("继承")
public class AnnotationTest {
@FirstAnnotation("那路多")
Student student;
@FirstAnnotation(value = "萨斯给",age = 18)
Student student1;
public void test(@IntDefAnnotationTest(value = 1) int a){
System.out.println(a);
}
}
public class AnnotationTest1 extends AnnotationTest {
}
public class AnnotationMainTest {
public static void main(String[] args) {
System.out.println(Arrays.toString(AnnotationTest1.class.getAnnotations()));
}
}
执行结果:
只有作用在类上的继承过来了。
@Repeatable
@Repeatable
是jdk8中新增的注解。在没有@Repeatable
注解的的注解中,在同一个地方使用相同的注解会报错,有了此元注解注解的注解,就可以在同一个地方使用相同的注解。
官方解释:
The annotation type {@code java.lang.annotation.Repeatable} is used to indicate that the annotation type whose declaration it (meta-)annotates is repeatable. The value of @Repeatable indicates the containing annotation type for the repeatable annotation type.
@Repeatable 注解是用于声明其它类型注解的元注解,来表示这个声明的注解是可重复的。@Repeatable的值是另一个注解,其可以通过这个另一个注解的值来包含这个可重复的注解。
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE,ElementType.FIELD})
@Repeatable(SecondAnnotation.class)
public @interface FirstAnnotation {
String value();
int age() default 18;
}
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE,ElementType.FIELD})
public @interface SecondAnnotation {
FirstAnnotation[] value();
}
其中,@
FirstAnnotation 注解上的元注解@Repeatable
中的值,使用了@
SecondAnnotation注解,@
SecondAnnotation注解中包含的值类型是一个@
FirstAnnotation注解的数组!
这就解释了官方文档中@Repeatable
中值的使用。
测试:
public class AnnotationTest {
@FirstAnnotation("1")
@FirstAnnotation("2")
@FirstAnnotation("3")
int a;
}
public class AnnotationMainTest {
public static void main(String[] args) {
Class annotationTestClass= AnnotationTest.class;
try {
Field field = annotationTestClass.getDeclaredField("a");
Annotation[] annotations = field.getAnnotations();
System.out.println(annotations.length);
System.out.println(Arrays.toString(annotations));
} catch (NoSuchFieldException e) {
e.printStackTrace();
}
}
}
执行:
因为int a属性上使用了3个@FirstAnnotation注解,所以猜测打印注解长度为3,然后打印详情,可是结果并不同。
1
[@com.example.fragmentadaptertest.annotation.SecondAnnotation(value=[@com.example.fragmentadaptertest.annotation.FirstAnnotation(age=18, value=1), @com.example.fragmentadaptertest.annotation.FirstAnnotation(age=18, value=2), @com.example.fragmentadaptertest.annotation.FirstAnnotation(age=18, value=3)])]
结果显示,int a属性
上的注解长度为 1 , 且打印信息为@SecondAnnontation
注解,它的值包含了使用的两个注解。因此可知在jdk8中,相同注解只是以集合的方式进行了保存,原理并没有变化。
注意事项
一些约束
@Repeatable 所声明的注解,其元注解@Target的使用范围要比@Repeatable的值声明的注解中的@Target的范围要大或相同,否则编译器错误,显示@Repeatable值所声明的注解的元注解@Target不是@Repeatable声明的注解的@Target的子集
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE,ElementType.FIELD})
@Repeatable(SecondAnnotation.class)
public @interface FirstAnnotation {
String value();
int age() default 18;
}
@Retention(RetentionPolicy.SOURCE)
@Target({ElementType.TYPE,ElementType.FIELD})
public @interface SecondAnnotation {
FirstAnnotation[] value();
}
简单说就是如果想让一个自定义注解A可以在一个地方使用,就打上@Repeatable标签,标签的值是一个注解B,注解B的
value的类型必须是A的数组类型。
最后使用多个A注解的实质,就是一个B注解类型包裹多个A注解类型的值。
想一想供需关系,A需要B来包裹实现注解数组,我们使用的是A在一个地方多次使用,就知道A在B必须在,A不在B可以在也可以不在。
所以B的存在级别要大于等于A。
RUNTIME > CLASS > SOURCE