文章目录
参考资料
- Java中的注解是如何工作的?
- Java基础加强总结(一)——注解(Annotation)
- Java注解教程
- Java Annotation认知(包括框架图、详细介绍、示例说明)
- Java Language Specification - Chapter 9. Interfaces
- Java魔法堂:注解用法详解——@SuppressWarnings
源码地址
概述
对于注解
而言,可以用一个词描述,那就是元数据
,即一种描述数据的数据。Annotations仅仅是元数据,和业务逻辑无关。Annotations仅仅提供它定义的属性(类/方法/包/域)的信息。
Annotations的使用者(javac编译器、开发工具和其他程序)来读取这些信息并实现必要的逻辑。
Annotation
public interface Annotation {
boolean equals(Object obj);
int hashCode();
String toString();
Class<? extends Annotation> annotationType();
}
Annotation是所有注解类型的公共接口,而且所有的注解必须实现这个接口。需要注意的是,由于注解类型声明是一种特殊的接口声明。为了区分注解类型声明与普通接口的声明,在关键字前面添加了@
用以区别,即@infterface
。 例如:
public @interface A {
}
上面代码中,声明了注解A
,通过@infterface
表明其实现了Annotation
接口及其是一个注解类。
元素
注解的本身是一种数据类型,是Annotation
的实现类,当然也可以在其内声明被称为元素
的成员。与普通数据类型不同的是,注解类型通过方法声明
定义注解类型的元素。例如:
public @interface B {
String name();
int version();
}
在注解B
中,声明了两个元素name
和version
,它们都是通过方法声明
的。
值的注意的是:
- 注解类型可以包含零个或多个元素。
- 注解类型的元素的数据类型为:基本类型、字符串(String)、类、类的任何参数化调用、枚举类型、注解类型或数组类型。
- 在注解类型中声明的任何方法不能覆盖在类Object或接口
java.lang.annotation.Annotation
中声明的任何公共或受保护的方法,否则出现编译时错误。 - 注解类型不能直接或间接泛型元素,否则出现编译时错误。
默认值
注解类型元素可以为其指定的默认值。在指定默认值时使用使用关键字default
和元素的默认值(空值或值列表)。其中
- 未指定默认值的元素,必须在使用时对该元素赋值。
- 给定的元素默认值的数据类型必须与元素的数据类型一致。
例如:
public @interface C {
String name() default "tea";
int version();
}
@C(version = 1)
public class CC {
@C(name = "2", version = 2)
String a;
}
在注解类型C
,中声明了两个元素name
和version
,其中,name
指定了默认值tea
。当使用C
时,可以不对元素name
赋值。当未对元素version
赋值时,IDE会报错,提示"version是必要的但是没有找到",也就意味着未指定默认值的元素,必须在使用时对该元素赋值。
用途
注解有多种用途,包括:
- 向编译器提供信息 —— 编译器使用注解检查错误或忽略警告
- 编译时和部署时处理 —— 注解处理工具可以处理注解信息来生成代码,XML文件等等
- 运行时处理 —— 一些注解在运行时进行审查
元注解
在注解类上使用另一个注解类,那么被使用的注解类就称为元注解.
Java SE API预定义了一组注解类型。某些注解用于Java编译器,一些适用于其他注解。
- @Documented
- @Target
- @Retention
- @Inherited
- @Override
- @SuppressWarnings
- @Deprecated
- @SafeVarargs
- @FunctionalInterface
- @Repeatable
@Documented
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Documented {
}
@Documented
用于注解类型的声明,表示是否将注解信息添加在java文档中。如果将@Documented
用于注解类型声明,那么该注解将成为javadoc的API中.声明Annotation时,@Documented
可有可无;若没有定义,则Annotation不会出现在javadoc中。
@Target
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Target {
ElementType[] value();
}
public enum ElementType {
// 用于描述类、接口或enum声明
TYPE,
// 用于描述实例变量
FIELD,
// 用于描述方法
METHOD,
// 用于描述参数
PARAMETER,
// 用于描述构造函数
CONSTRUCTOR,
// 用于描述局部变量
LOCAL_VARIABLE,
// 用于描述注解类型
ANNOTATION_TYPE,
// 用于记录java文件的package信息
PACKAGE,
/**
* 用于注解类型参数(泛型)
* @since 1.8
*/
TYPE_PARAMETER,
/**
* Use of a type
*
* @since 1.8
*/
TYPE_USE
}
@Target
用于注解类型的声明,表明注解类型用于注解哪些位置(Class、实例变量、方法、参数 、构造函数、局部变量、注解类型、包及任何位置)。如果不明确指出,该注解可以放在任何地方。需要说明的是:属性的注解是兼容的,如果想注解哪些类型,只需将其一一添加至value
值列表即可。
- ElementType.TYPE:用于注解类、接口或enum声明
- ElementType.FIELD:用于注解实例变量
- ElementType.METHOD:用于注解方法
- ElementType.PARAMETER:用于注解参数
- ElementType.CONSTRUCTOR:用于注解构造函数
- ElementType.LOCAL_VARIABLE:用于注解局部变量
- ElementType.ANNOTATION_TYPE:用于注解注解类型
- ElementType.PACKAGE:用于注解java文件的package信息
- ElementType.TYPE_PARAMETER:用于注解类型参数(泛型)
- ElementType.TYPE_USE:用于注解任何类型
@Retention
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Retention {
RetentionPolicy value();
}
@Retention
用于注解类型的声明,表明注解类型的注解的保留时长,也可以认为被其注解的注解类型的生命周期.对于保留策略,通过其RetentionPolicy类型的value
元素指定.如果注解类型声明中没有被@Retention
注解,保留策略默认为RetentionPolicy.CLASS
。
public enum RetentionPolicy {
SOURCE,
CLASS,
RUNTIME
}
其中:
- RetentionPolicy.SOURCE —> 注解只保留在源文件,当
.java文件
编译成.class文件
时,注解被遗弃。 - RetentionPolicy.CLASS —> 注解被保留到
.class文件
中,但JVM加载.class文件
时被遗弃,这是默认的生命周期。 - RetentionPolicy.RUNTIME —> 注解不仅被保存到
.class文件
中,JVM加载.class文件
之后,注解仍然存在。
为什么会有保留策略这个概念呢?因为Java程序从源文件创建到程序运行要经过3个阶段:
- Java源文件,即
.java文件
- javac把java源文件(
.java文件
)编译成字节码文件即.class文件
- java虚拟机(JVM)解释运行字节码,此时字节码已加载到内存,为了便于理解,称之为
内存中的字节码
对于注解而言,在每个阶段中,都可能将注解去掉,也有可能把注解保留下来,这样造成了注解的3个阶段:
.java源文件
.class文件
内存中的字节码
在编写代码时通过注解检查代码,需要在编写Java源码
时运行,对应的就是SOURCE
阶段。
需要在编译前进行一些预处理,对应的是CLASS
阶段,编译时注解对代码的预处理发生在此阶段。
在程序运行后,想要动态获取一些注解信息,只能在RUNTIME
阶段,例如,EventBus 2.x
通过动态注解获取事件信息并添加到事件列表中 。
@Inherited
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Inherited {
}
@Inherited
用于声明自定义注解(比如C),当C注解超类时,其子类可以从超类继承C。如果C没有被@Inherited
注解,当C注解超类时,子类不可以从超类继承C。例如:
// 声明的此注解没有使用@Inherited,表示此注解用在类上时,不会被子类所继承
@Retention(RetentionPolicy.RUNTIME)
public @interface C {
String name() default "tea";
int version();
}
// 声明的此注解使用了@Inherited,表示此注解用在类上时,会被子类所继承
@Retention(RetentionPolicy.RUNTIME)
@Inherited
public @interface D {
}
@D
@C(version = 1)
public class Person
public class Student extends Person
public class TestInherited {
public static void main(String[] args) {
Class clz = Student.class;
// Log: Student is annotated by C: false
System.out.println("Student is annotated by C: " + clz.isAnnotationPresent(C.class));
// Student is annotated by D: true
System.out.println("Student is annotated by D: " + clz.isAnnotationPresent(D.class));
}
}
输出结果:
Student is annotated by C: false
Student is annotated by D: true
注意:
- 使用
@Inherited
声明的自定义注解只能用于注解Class.
@Override
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public @interface Override {
}
@Override
用于注解方法,表明该方法继承自父类。使用该注解的用途为:
- 检查是否正确重写父类的方法
- 便于阅读代码,表明该方法继承父类
比如,在父类Person
中声明方法doSwim()
public class Person {
public void doSwim() {
}
}
此时,创建子类Student
,继承自Person
,当重写doSwim()
时,如果没有使用@Override
注解,在不看Person
源码的前提下,第一反映应该是,doSwim()
是Student
的成员方法,而不是重写父类的方法:
public class Student extends Person{
public void doSwim() {
}
}
但是,如果使用了@Override
注解,任何看代码的人都知道doSwim()
的是继承自父类的。
public class Student extends Person{
@Override
public void doSwim() {
}
}
另外,在重写父类方法时,参数写错,如果没有添加@Override
注解,IDE不会有任何提示。
public class Student extends Person{
@Override
public void doSwim(float a) {
}
}
当添加@Override
注解后,IDE会给出错误提示,如下:
@SuppressWarnings
@Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE})
@Retention(RetentionPolicy.SOURCE)
public @interface SuppressWarnings {
String[] value();
}
@SuppressWarnings
用于在注解的元素(以及注解元素中所有程序代码)中抑制编译器产生的指定警告信息,不用在编译完成后出现警告信息。从@SuppressWarnings
的源码中可以看出其注解目标为类、字段、函数、函数入参、构造函数和函数的局部变量。
例如:
- 抑制单类型的警告
@SuppressWarnings("unchecked")
public void addItems(String item){
@SuppressWarnings("rawtypes")
List items = new ArrayList();
items.add(item);
}
- 抑制多类型的警告
@SuppressWarnings(value={"unchecked", "rawtypes"})
public void addItems(String item){
List items = new ArrayList();
items.add(item);
}
- 抑制所有类型的警告
@SuppressWarnings("all")
public void addItems(String item){
List items = new ArrayList();
items.add(item);
}
抑制警告的关键字
关键字 | 用途 |
---|---|
all | to suppress all warnings |
boxing | to suppress warnings relative to boxing/unboxing operations |
cast | to suppress warnings relative to cast operations |
dep-ann | to suppress warnings relative to deprecated annotation |
deprecation | to suppress warnings relative to deprecation |
fallthrough | to suppress warnings relative to missing breaks in switch statements |
finall | to suppress warnings relative to finally block that don’t return |
hiding | to suppress warnings relative to locals that hide variable |
incomplete-switch | to suppress warnings relative to missing entries in a switch statement (enum case) |
nls | to suppress warnings relative to non-nls string literals |
null | to suppress warnings relative to null analysis |
rawtypes | to suppress warnings relative to un-specific types when using generics on class params |
restriction | to suppress warnings relative to usage of discouraged or forbidden references |
serial | to suppress warnings relative to missing serialVersionUID field for a serializable class |
static-access | to suppress warnings relative to incorrect static access |
synthetic-access | to suppress warnings relative to unoptimized access from inner classes |
unchecked | to suppress warnings relative to unchecked operations |
unqualified-field-access | to suppress warnings relative to field access unqualified |
unused | to suppress warnings relative to unused code |
@Deprecated
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(value={CONSTRUCTOR, FIELD, LOCAL_VARIABLE, METHOD, PACKAGE, PARAMETER, TYPE})
public @interface Deprecated {
}
@Deprecated
用于注解构造函数、成员变量、局部变量、方法、包、参数以及类、接口或enum声明,表明其注解的元素(以及注解元素中所有程序代码)存在隐患或者存在更好的替代方案,不再建议使用。在新版本中有其他方法或类可以代替这个使用,以后的版本也不会再更新。在使用@Deprecated注解的元素时,IDE会给出警告,以删除线
提示。例如:
@Deprecated
public class Student extends Person{
@Deprecated
public void doStudy() {
}
}
在Student中,使用@Deprecated
注解了Class和方法doStudy()
,IDE会给出警告提示,如下所示:
@SafeVarargs
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.CONSTRUCTOR, ElementType.METHOD})
public @interface SafeVarargs {}
@SafeVarargs
用于可变参数注解构造函数或方法,断言代码不会对其varargs参数
执行潜在的不安全操作。当使用此注解时,将抑制与varargs
使用相关的未检查的警告,并禁止在调用位置创建有关参数化数组的未经检查的警告。
public class SafeVarargsDemo {
@SafeVarargs
public SafeVarargsDemo(List<String> ...args) {
}
@SafeVarargs
public static void doSwim(List<String>... args) {
System.out.println(Arrays.toString(args));
}
public static void main(String[] args) {
Class clz = SafeVarargsDemo.class;
try {
Method method = clz.getMethod("doSwim", List[].class);
System.out.println("parameter type: " + Arrays.toString(method.getParameterTypes()));
} catch (NoSuchMethodException e) {
e.printStackTrace();
}
}
}
// Log
constructor - name: com.teaphy.demo.SafeVarargsDemo, parameter type: [class [Ljava.util.List;]
method - name: doSwim, parameter type: [class [Ljava.util.List;]
因为泛型的擦除原则,当使用泛型作为可变参数时,参数数组中存储的是不可具体化的泛型类对象,在编译之后泛型会被擦出掉,那么参数数组存在类型安全问题。因此编译器会给出相应的警告消息。
值得注意的是:
@SafeVarargs
注解的元素必须是可变参数方法和构造器- 如果
@SafeVarargs
注解的是可变参数的方法,那么该方法必须是被static或final修饰的
@FunctionalInterface
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface FunctionalInterface {}
@FunctionalInterface
用于注解接口类型声明,表明其为Java语言规范定义的函数式接口。所谓函数式接口就是只有一个抽象方法的接口。不管是默认方法还是静态方法都是有实现的,它们都不是抽象的。另外,如果接口覆盖了java.lang.Object
的方法,也不计入抽象方法的的计数。
例如:
@FunctionalInterface
public interface TestFunctionalInterface {
void doTes();
/**
* default不是抽象方法
*/
default void doA() {
}
/**
* static不是抽象方法
*/
static void doB() {
}
/**
* java.lang.Object中的方法不是抽象方法
*/
@Override
boolean equals(Object obj);
}
值得注意的是:
1. `@FunctionalInterface`注解的类型只能是接口类型声明,而不是类型是注解类型,枚举或类。 2. `@FunctionalInterface`注解的类型必须满足函数式接口的要求。@Repeatable
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Repeatable {
/**
* Indicates the <em>containing annotation type</em> for the
* repeatable annotation type.
* @return the containing annotation type
*/
Class<? extends Annotation> value();
}
@Repeatable
用于注解注解类型,表明该注解可以重复注解同一个元素。@Repeatable的值表示可重复注解类型的容器,其元素为可重复注解类型。
@Repeatable
是JDK 1.8新增的元注解,在此之前一个类型的注解不能重复修饰在相同的注解。
可重复注解的实现:
- 声明可重复注解类型的容器 - Es
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD, ElementType.TYPE})
public @interface Es {
E[] value();
}
值得注意的是:
- 可重复注解类型的容器的也是一个注解,表明它可以直接注解元素,而其
value
值就是可重复注解的数组。 - 可重复注解类型的容器的value方法(默认方法)需要返回其元注解(即可重复注解类)的列表,否则这个容器类创建的不符合需求。
- 声明可重复注解类型 - E
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD, ElementType.TYPE})
@Repeatable(Es.class)
public @interface E {
String value();
}
可重复注解与普通注解没有太大区别,值得注意的是,使用@Repeatable
表示其可以重复注解元素,还要指定可重复注解类型的容器,比如这里指定的是Es.class
- 使用可重复注解
@E("A")
@E("B")
@E("C")
@E("D")
public class EE {
}
使用E
注解EE,共重复了4次,其value值分别为A,B,C,D。
- 获取重复注解
public class RepeatableDemo {
public static void main(String[] args) {
Class<EE> clz = EE.class;
if (clz.isAnnotationPresent(Es.class)) {
// 返回重复注解(E.class)列表
E[] annotationsByType = clz.getAnnotationsByType(E.class);
System.out.println("annotationsByType: " + Arrays.toString(annotationsByType));
}
}
}
// 运行结果
annotationsByType: [
@com.teaphy.annotation.E(value=A),
@com.teaphy.annotation.E(value=B),
@com.teaphy.annotation.E(value=C),
@com.teaphy.annotation.E(value=D)]
通过反射获取了EE中的返回重复注解(E.class)列表。但是,在断言EE
是否被某个注解类型注解时,使用的是Es.class
(即可重复注解类型的容器)而不是E.class
,这是为什么呢?如果查看编译后的EE.class
,可以发现,编译器将重复的注解E
装入了可重复注解类型的容器Es
,实际注解EE.class
的是Es
而不是E
。
检索注解
java.lang.reflect
Interface AnnotatedElement
子接口:
AnnotatedArrayType, AnnotatedParameterizedType, AnnotatedType, AnnotatedTypeVariable, AnnotatedWildcardType, GenericDeclaration, TypeVariable<D>
直接实现类:
AccessibleObject, Class, Constructor, Executable, Field, Method, Package, Parameter
在java.lang.reflect
中定义了AnnotatedElement
接口,从而允许在带有注解的元素通过反射的API获取注解。此接口中的方法返回的所有注解都是不可变的和可序列化的。
其中,getAnnotationsByType(Class)
和getDeclaredAnnotationsByType(Class)
方法允许元素获取相同类型的多个注解。如果任一方法的参数是可重复的注解类型,那么该方法将搜索可重复注解类型的容器
是否存在,如果存在,将返回可重复注解类型的容器
内的所有注解。
使用直接存在、间接存在、存在和关联的术语来精确描述方法返回的注解和元素之间的关系:
- 直接存在 - 如果E具有
RuntimeVisibleAnnotations
或RuntimeVisibleParameterAnnotations
或RuntimeVisibleTypeAnnotations
属性,并且该属性包含A,则注解A直接存在元素E上。 - 间接存在 - 如果E具有
RuntimeVisibleAnnotations
或RuntimeVisibleParameterAnnotations
或RuntimeVisibleTypeAnnotations
属性,并且A的类型是可重复的,并且该属性仅包含一个注解,其值元素包含A且其类型包含A注解类型(这个注解其实就是可重复注解A的容器),则注解A间接存在于元素E上。 - 存在 - 注解A存在元素E上,那么
- A直接存在于元素E上
- A的类型没有注解元素E,但,E是一个类,A的类型是可继承的,A存在于E的超类中。
- 关联 - 注解A与元素E关联,那么
- A直接或间接存在于E
- A没有直接或间接存在于E上,但,E是一个类,A的类型是可继承的,A与E的超类相关联。
方法 | 直接存在 | 间接存在 | 存在 | 关联 |
---|---|---|---|---|
T getAnnotation(Class) | X | |||
Annotation[] getAnnotations() | X | |||
T[] getAnnotationsByType(Class) | X | |||
T getDeclaredAnnotation(Class) | X | |||
Annotation[] getDeclaredAnnotations() | X | |||
T[] getDeclaredAnnotationsByType(Class) | X | X |
比如:
@Retention(RetentionPolicy.RUNTIME)
public @interface C {
String name() default "tea";
int version();
}
@C(version = 1)
public class Person {
}
public class AnnotationDemo {
public static void main(String[] args) {
Class clzPerson = Person.class;
Annotation annotation = clzPerson.getAnnotation(C.class);
if (null != annotation) {
C c = (C) annotation;
System.out.println("c: " + c);
}
}
}