注解
Java 注解(Annotation)又称 Java 标注,是 JDK5.0 引入的一种注释机制。
Java 语言中的类、方法、变量、参数和包等都可以被标注。和 Javadoc 不同,Java 标注可以通过反射获取标注内容。在编译器生成类文件时,标注可以被嵌入到字节码中。Java 虚拟机可以保留标注内容,在运行时可以获取到标注内容 。 同时也支持自定义 Java 标注。
一、本质
注解的本质是一个Annotation接口
/**
* The common interface extended by all annotation types.
*/
public interface Annotation {
/**
* 如果指定的对象表示在逻辑上与此注解等效的注解,则返回true;否则返回false
*/
boolean equals(Object obj);
/**
* @return the hash code of this annotation
*/
int hashCode();
/**
* @return a string representation of this annotation
*/
String toString();
/**
* Returns the annotation type of this annotation.
* @return the annotation type of this annotation
*/
Class<? extends Annotation> annotationType();
}
所有的注解都继承自 Annotation
接口
@Override
的本质就是继承了 Annotation
的接口
public interface Override extends Annotation {
}
注解产生作用主要有两个方式,一是在编译期扫描,另一个是运行期反射。
简明的说,java在编译字节码的时候,一旦遇到注解修饰的类或者方法,就会做出相应的处理,比如 @Override
注解,编译器就会检查当前方法的方法签名是否真正重写了父类的某个方法,也就是比较父类中是否具有一个同样的方法签名。但是这种情况一般只发生在内置注解上,自定义的注解是没有作用的。
另一种就是运行期间反射。
主要方法有几个
getAnnotation
:返回指定的注解isAnnotationPresent
:判定当前元素是否被指定注解修饰getAnnotations
:返回所有的注解getDeclaredAnnotation
:返回本元素的指定注解getDeclaredAnnotations
:返回本元素的所有注解,不包含父类继承而来的
代码示例:
自定义注解:
@Target(value = {ElementType.FIELD,ElementType.METHOD,ElementType.TYPE})//使用的地方
@Retention(value = RetentionPolicy.RUNTIME)//一直存在,javap反编译字节码可以看到
public @interface Helloqs {
String name();
}
反射于注解:
@Helloqs(name = "clazz")
public class MyAnnotation {
String sky;
@Helloqs(name = "filed")
String qs;
public static void main(String[] args) {
//通过反射获取字节码,并获取对应元素的注解
Class<MyAnnotation> clz = MyAnnotation.class;
//1.判定当前元素是否被指定注解修饰
boolean annotationFlag = clz.isAnnotationPresent(Helloqs.class);
System.out.println("annotationFlag:"+annotationFlag);
if (annotationFlag) {
//获取注解
Helloqs helloqs = clz.getAnnotation(Helloqs.class);
System.out.println("helloqs:"+helloqs);
//获取注解属性
String name = helloqs.name();
System.out.println("name:"+name);
}
//2.获取所有字段
Field[] field = clz.getDeclaredFields();
for (Field field2 : field) {
//如果有注解就打印
if(field2.isAnnotationPresent(Helloqs.class)) {
//获取注解,打印
Helloqs qs = field2.getAnnotation(Helloqs.class);
System.out.println("field.qs:"+qs.name());
}
}
//3.获取所有方法
Method[] methods = clz.getMethods();
for (Method method : methods) {
//如果有注解就打印
if(method.isAnnotationPresent(Helloqs.class)) {
//获取注解,打印
Helloqs qs = method.getAnnotation(Helloqs.class);
System.out.println("method.qs:"+qs.name());
}
}
}
@Helloqs(name = "method")
public static void test() {
}
}
二、用途
- 编译格式检查
- 反射中解析
- 生成帮助文档
- 跟踪代码依赖
三、内置注解
- @Override : 重写
- 定义在 java.lang.Override
- @Deprecated:废弃
- 定义在 java.lang.Deprecated
- @SafeVarargs
- Java 7 开始支持,忽略任何使用参数为泛型变量的方法或构造函数调用产生的警告。
- @FunctionalInterface: 函数式接口
- Java 8 开始支持,标识一个匿名函数或函数式接口。
- @Repeatable:标识某注解可以在同一个声明上使用多次
- Java 8 开始支持,标识某注解可以在同一个声明上使用多次。
- @SuppressWarnings:抑制编译时的警告信息
- 定义在 java.lang.SuppressWarnings
- 三种使用方式
- @SuppressWarnings(“unchecked”) [^ 抑制单类型的警告]
- @SuppressWarnings(“unchecked”,“rawtypes”) [^ 抑制多类型的警告]
- @SuppressWarnings(“all”) [^ 抑制所有类型的警告]
- 参数列表,参看相关资料,略。
四、元注解
是用于修饰注解的注解,通常用在注解的定义上,jdk源码:
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public @interface Override {
}
-
@Target:注解的作用目标
用于解释这个注解用于修饰方法还是类还是变量
@Documented @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.ANNOTATION_TYPE) public @interface Target { /** * Returns an array of the kinds of elements an annotation type * can be applied to. * @return an array of the kinds of elements an annotation type * can be applied to */ ElementType[] value(); }
其中有一个 value 可以通过
@Target(value = {ElementType.PACKAGE})
传值,这里就表示,这个注解只可以用在包上 -
@Retention:注解的生命周期
指明当前注解的生命周期
@Documented @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.ANNOTATION_TYPE) public @interface Retention { /** * Returns the retention policy. * @return the retention policy */ RetentionPolicy value(); }
RetentionPolicy 也是一个枚举,里面指定了注解的生命周期,编译期可见还是类加载是可见,或者
RetentionPolicy.RUNTIME
在运行时器,也就是永久存在,可以被反射获取 -
@Documented:注解是否应当被包含在 JavaDoc 文档中
-
@Inherited:是否允许子类继承该注解
- 子类会继承父类使用的注解中被@Inherited修饰的注解
- 接口继承关系中,子接口不会继承父接口中的任何注解,不管父接口中使用的注解有没有 被@Inherited修饰
- 类实现接口时不会继承任何接口中定义的注解
五、注解相关枚举
-
ElementType(注解的用途类型),JDK9 新增了一个
MODULE
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 (标注参数类型)*/ TYPE_PARAMETER, /** Use of a type (标注任何类型名称)*/ TYPE_USE, /** * Module declaration.(jdk 9 新增,标注模块声明) * * @since 9 */ MODULE }
-
RetentionPolicy(注解作用域策略)
public enum RetentionPolicy { /** Annotations are to be discarded by the compiler. (注解在编译期可见,不会写入 class 文件)*/ 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 文件,默认行为) */ 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.(保存在class文件中,可以被反射获取) * * @see java.lang.reflect.AnnotatedElement */ RUNTIME }
六、自定义注解
- 定义的注解,自动继承了
java.lang,annotation.Annotation
接口 - 注解中的每一个方法,实际是声明的注解配置参数
- 方法的名称就是配置参数的名称
- 方法的返回值类型,就是配置参数的类型。只能是:基本类型/Class/String/enum
- 可以通过
default
来声明参数的默认值 - 如果只有一个参数成员,一般参数名为
value
- 注解元素必须要有值,我们定义注解元素时,经常使用空字符串、0作为默认值。
栗子:
@Target(value = {ElementType.FIELD,ElementType.METHOD,ElementType.TYPE})//使用的地方
@Retention(value = RetentionPolicy.RUNTIME)//一直存在
public @interface Helloqs {
String name();
}
反编译看到注解信息被写进class文件:
Classfile /D:/eclipse-workspace/xzkproject/src/annotation/helloqs.class
Last modified 2020年10月7日; size 441 bytes
MD5 checksum 564b7874aa5fa26327f5316d2e6e788f
Compiled from "helloqs.java"
public interface annotation.Helloqs extends java.lang.annotation.Annotation
minor version: 0
major version: 55
flags: (0x2601) ACC_PUBLIC, ACC_INTERFACE, ACC_ABSTRACT, ACC_ANNOTATION
this_class: #1 // annotation/Helloqs
super_class: #2 // java/lang/Object
interfaces: 1, fields: 0, methods: 1, attributes: 2
Constant pool:
#1 = Class #18 // annotation/Helloqs
#2 = Class #19 // java/lang/Object
#3 = Class #20 // java/lang/annotation/Annotation
#4 = Utf8 name
#5 = Utf8 ()Ljava/lang/String;
#6 = Utf8 SourceFile
#7 = Utf8 helloqs.java
#8 = Utf8 RuntimeVisibleAnnotations
#9 = Utf8 Ljava/lang/annotation/Target;
#10 = Utf8 value
#11 = Utf8 Ljava/lang/annotation/ElementType;
#12 = Utf8 FIELD
#13 = Utf8 METHOD
#14 = Utf8 TYPE
#15 = Utf8 Ljava/lang/annotation/Retention;
#16 = Utf8 Ljava/lang/annotation/RetentionPolicy;
#17 = Utf8 RUNTIME
#18 = Utf8 annotation/Helloqs
#19 = Utf8 java/lang/Object
#20 = Utf8 java/lang/annotation/Annotation
{
public abstract java.lang.String name();
descriptor: ()Ljava/lang/String;
flags: (0x0401) ACC_PUBLIC, ACC_ABSTRACT
}
SourceFile: "helloqs.java"
RuntimeVisibleAnnotations:
0: #9(#10=[e#11.#12,e#11.#13,e#11.#14])
java.lang.annotation.Target(
value=[Ljava/lang/annotation/ElementType;.FIELD,Ljava/lang/annotation/ElementType;.METHOD,Ljava/lang/annotation/ElementType;.TYPE]
)
1: #15(#10=e#16.#17)
java.lang.annotation.Retention(
value=Ljava/lang/annotation/RetentionPolicy;.RUNTIME
)
分析:都是很熟悉的东西,因为 RetentionPolicy.RUNTIME
,所以注解信息被写进了字节码文件中。注意到第20行RuntimeVisibleAnnotations,虚拟机规范中说明,字节码文件会读取 RuntimeVisibleAnnotations
, RuntimeInvisibleAnnotations
, RuntimeVisibleParameterAnnotations
, RuntimeInvisibleParameterAnnotations
and AnnotationDefault
几个值也就是标注了注解的“可见性”。