java_annotations
对于注解
而言,可以用一个词描述,那就是元数据
,即一种描述数据的数据。Annotations仅仅是元数据,和业务逻辑无关。Annotations仅仅提供它定义的属性(类/方法/包/域)的信息。
Annotations的使用者(javac编译器、开发工具和其他程序)来读取这些信息并实现必要的逻辑。
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
,它们都是通过方法声明
的。
值的注意的是:
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是必要的但是没有找到",也就意味着未指定默认值的元素,必须在使用时对该元素赋值。
注解有多种用途,包括:
在注解类上使用另一个注解类,那么被使用的注解类就称为元注解.
Java SE API预定义了一组注解类型。某些注解用于Java编译器,一些适用于其他注解。
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Documented {
}
@Documented
用于注解类型的声明,表示是否将注解信息添加在java文档中。如果将@Documented
用于注解类型声明,那么该注解将成为javadoc的API中.声明Annotation时,@Documented
可有可无;若没有定义,则Annotation不会出现在javadoc中。
@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
值列表即可。
@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
}
其中:
.java文件
编译成.class文件
时,注解被遗弃。.class文件
中,但JVM加载.class文件
时被遗弃,这是默认的生命周期。.class文件
中,JVM加载.class文件
之后,注解仍然存在。为什么会有保留策略这个概念呢?因为Java程序从源文件创建到程序运行要经过3个阶段:
.java文件
.java文件
)编译成字节码文件即.class文件
内存中的字节码
对于注解而言,在每个阶段中,都可能将注解去掉,也有可能把注解保留下来,这样造成了注解的3个阶段:
.java源文件
.class文件
内存中的字节码
在编写代码时通过注解检查代码,需要在编写Java源码
时运行,对应的就是SOURCE
阶段。
需要在编译前进行一些预处理,对应的是CLASS
阶段,编译时注解对代码的预处理发生在此阶段。
在程序运行后,想要动态获取一些注解信息,只能在RUNTIME
阶段,例如,EventBus 2.x
通过动态注解获取事件信息并添加到事件列表中 。
@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.@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会给出错误提示,如下:
@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 |
@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会给出警告提示,如下所示:
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.CONSTRUCTOR, ElementType.METHOD})
public @interface SafeVarargs {}
@SafeVarargs
用于可变参数注解构造函数或方法,断言代码不会对其varargs参数
执行潜在的不安全操作。当使用此注解时,将抑制与varargs
使用相关的未检查的警告,并禁止在调用位置创建有关参数化数组的未经检查的警告。
public class SafeVarargsDemo {
@SafeVarargs
public SafeVarargsDemo(List ...args) {
}
@SafeVarargs
public static void doSwim(List... 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修饰的@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`注解的类型必须满足函数式接口的要求。@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Repeatable {
/**
* Indicates the containing annotation type for the
* repeatable annotation type.
* @return the containing annotation type
*/
Class extends Annotation> value();
}
@Repeatable
用于注解注解类型,表明该注解可以重复注解同一个元素。@Repeatable的值表示可重复注解类型的容器,其元素为可重复注解类型。
@Repeatable
是JDK 1.8新增的元注解,在此之前一个类型的注解不能重复修饰在相同的注解。
可重复注解的实现:
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD, ElementType.TYPE})
public @interface Es {
E[] value();
}
值得注意的是:
value
值就是可重复注解的数组。@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 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
直接实现类:
AccessibleObject, Class, Constructor, Executable, Field, Method, Package, Parameter
在java.lang.reflect
中定义了AnnotatedElement
接口,从而允许在带有注解的元素通过反射的API获取注解。此接口中的方法返回的所有注解都是不可变的和可序列化的。
其中,getAnnotationsByType(Class)
和getDeclaredAnnotationsByType(Class)
方法允许元素获取相同类型的多个注解。如果任一方法的参数是可重复的注解类型,那么该方法将搜索可重复注解类型的容器
是否存在,如果存在,将返回可重复注解类型的容器
内的所有注解。
使用直接存在、间接存在、存在和关联的术语来精确描述方法返回的注解和元素之间的关系:
RuntimeVisibleAnnotations
或RuntimeVisibleParameterAnnotations
或RuntimeVisibleTypeAnnotations
属性,并且该属性包含A,则注解A直接存在元素E上。RuntimeVisibleAnnotations
或RuntimeVisibleParameterAnnotations
或RuntimeVisibleTypeAnnotations
属性,并且A的类型是可重复的,并且该属性仅包含一个注解,其值元素包含A且其类型包含A注解类型(这个注解其实就是可重复注解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);
}
}
}