# java_annotations **Repository Path**: teaphy_admin/java_annotations ## Basic Information - **Project Name**: java_annotations - **Description**: 注解 - **Primary Language**: Java - **License**: Apache-2.0 - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2019-03-13 - **Last Updated**: 2020-12-19 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # 参考资料 1. [Java中的注解是如何工作的?](http://www.importnew.com/10294.html) 2. [Java基础加强总结(一)——注解(Annotation)](https://www.cnblogs.com/xdp-gacl/p/3622275.html) 3. [Java注解教程](https://blog.csdn.net/lihenair/article/details/51134716) 4. [Java Annotation认知(包括框架图、详细介绍、示例说明)](https://www.cnblogs.com/skywang12345/p/3344137.html) 5. [Java Language Specification - Chapter 9. Interfaces ](https://docs.oracle.com/javase/specs/jls/se7/html/jls-9.html#jls-9.6) 6. [Java魔法堂:注解用法详解——@SuppressWarnings](https://www.cnblogs.com/fsjohnhuang/p/4040785.html) # 源码地址 [java_annotations](https://gitee.com/teaphy/java_annotations) # 概述 对于`注解`而言,可以用一个词描述,那就是`元数据`,即一种描述数据的数据。Annotations仅仅是元数据,和业务逻辑无关。Annotations仅仅提供它定义的属性(类/方法/包/域)的信息。 Annotations的使用者(javac编译器、开发工具和其他程序)来读取这些信息并实现必要的逻辑。 ## Annotation ``` public interface Annotation { boolean equals(Object obj); int hashCode(); String toString(); Class annotationType(); } ``` Annotation是所有注解类型的公共接口,而且所有的注解必须实现这个接口。需要注意的是,由于注解类型声明是一种特殊的接口声明。为了区分注解类型声明与普通接口的声明,在关键字前面添加了`@`用以区别,即`@infterface`。 例如: ``` public @interface A { } ``` 上面代码中,声明了注解`A`,通过`@infterface`表明其实现了`Annotation`接口及其是一个注解类。 ## 元素 注解的本身是一种数据类型,是`Annotation`的实现类,当然也可以在其内声明被称为`元素`的成员。与普通数据类型不同的是,注解类型通过`方法声明`定义注解类型的元素。例如: ``` public @interface B { String name(); int version(); } ``` 在注解`B`中,声明了两个元素`name`和`version`,它们都是通过`方法声明`的。 值的注意的是: 1. 注解类型可以包含零个或多个元素。 2. 注解类型的元素的数据类型为:基本类型、字符串(String)、类、类的任何参数化调用、枚举类型、注解类型或数组类型。 3. 在注解类型中声明的任何方法不能覆盖在类Object或接口`java.lang.annotation.Annotation`中声明的任何公共或受保护的方法,否则出现编译时错误。 4. 注解类型不能直接或间接泛型元素,否则出现编译时错误。 ### 默认值 注解类型元素可以为其指定的默认值。在指定默认值时使用使用关键字`default`和元素的默认值(空值或值列表)。其中 1. 未指定默认值的元素,必须在使用时对该元素赋值。 2. 给定的元素默认值的数据类型必须与元素的数据类型一致。 例如: ``` 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是必要的但是没有找到",也就意味着未指定默认值的元素,必须在使用时对该元素赋值。 ![image](https://gitee.com/teaphy/java_annotations/raw/master/imgs/default_value_c.png) ## 用途 注解有多种用途,包括: - 向编译器提供信息 —— 编译器使用注解检查错误或忽略警告 - 编译时和部署时处理 —— 注解处理工具可以处理注解信息来生成代码,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个阶段: 1. Java源文件,即`.java文件` 2. javac把java源文件(`.java文件`)编译成字节码文件即`.class文件` 3. java虚拟机(JVM)解释运行字节码,此时字节码已加载到内存,为了便于理解,称之为`内存中的字节码` 对于注解而言,在每个阶段中,都可能将注解去掉,也有可能把注解保留下来,这样造成了注解的3个阶段: 1. `.java源文件` 2. `.class文件` 3. `内存中的字节码` 在编写代码时通过注解检查代码,需要在编写`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`用于注解方法,表明该方法继承自父类。使用该注解的用途为: 1. 检查是否正确重写父类的方法 2. 便于阅读代码,表明该方法继承父类 比如,在父类`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会给出错误提示,如下: ![image](https://gitee.com/teaphy/java_annotations/raw/master/imgs/overrider.png) ## @SuppressWarnings ``` @Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE}) @Retention(RetentionPolicy.SOURCE) public @interface SuppressWarnings { String[] value(); } ``` `@SuppressWarnings`用于在注解的元素(以及注解元素中所有程序代码)中抑制编译器产生的指定警告信息,不用在编译完成后出现警告信息。从`@SuppressWarnings`的源码中可以看出其注解目标为类、字段、函数、函数入参、构造函数和函数的局部变量。 例如: 1. 抑制单类型的警告 ``` @SuppressWarnings("unchecked") public void addItems(String item){ @SuppressWarnings("rawtypes") List items = new ArrayList(); items.add(item); } ``` 2. 抑制多类型的警告 ``` @SuppressWarnings(value={"unchecked", "rawtypes"}) public void addItems(String item){ List items = new ArrayList(); items.add(item); } ``` 3. 抑制所有类型的警告 ``` @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会给出警告提示,如下所示: ![image](https://gitee.com/teaphy/java_annotations/raw/master/imgs/deprcated.png) ## @SafeVarargs ``` @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;] ``` 因为泛型的擦除原则,当使用泛型作为可变参数时,参数数组中存储的是不可具体化的泛型类对象,在编译之后泛型会被擦出掉,那么参数数组存在类型安全问题。因此编译器会给出相应的警告消息。 值得注意的是: 1. `@SafeVarargs`注解的元素必须是可变参数方法和构造器 2. 如果`@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 containing annotation type for the * repeatable annotation type. * @return the containing annotation type */ Class value(); } ``` `@Repeatable`用于注解注解类型,表明该注解可以重复注解同一个元素。@Repeatable的值表示可重复注解类型的容器,其元素为可重复注解类型。 `@Repeatable`是JDK 1.8新增的元注解,在此之前一个类型的注解不能重复修饰在相同的注解。 可重复注解的实现: 1. 声明可重复注解类型的容器 - Es ``` @Documented @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.FIELD, ElementType.TYPE}) public @interface Es { E[] value(); } ``` 值得注意的是: - 可重复注解类型的容器的也是一个注解,表明它可以直接注解元素,而其`value`值就是可重复注解的数组。 - 可重复注解类型的容器的value方法(默认方法)需要返回其元注解(即可重复注解类)的列表,否则这个容器类创建的不符合需求。 2. 声明可重复注解类型 - E ``` @Documented @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.FIELD, ElementType.TYPE}) @Repeatable(Es.class) public @interface E { String value(); } ``` 可重复注解与普通注解没有太大区别,值得注意的是,使用`@Repeatable`表示其可以重复注解元素,还要指定可重复注解类型的容器,比如这里指定的是`Es.class` 3. 使用可重复注解 ``` @E("A") @E("B") @E("C") @E("D") public class EE { } ``` 使用`E`注解EE,共重复了4次,其value值分别为A,B,C,D。 4. 获取重复注解 ``` 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`。 ![image](https://gitee.com/teaphy/java_annotations/raw/master/imgs/repeatable.png) # 检索注解 ``` 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)`方法允许元素获取相同类型的多个注解。如果任一方法的参数是可重复的注解类型,那么该方法将搜索`可重复注解类型的容器`是否存在,如果存在,将返回`可重复注解类型的容器`内的所有注解。 使用直接存在、间接存在、存在和关联的术语来精确描述方法返回的注解和元素之间的关系: - 直接存在 - 如果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); } } } ```