JAVA注解学习笔记

409 阅读9分钟

1. 注解

   Java Language Specification (JLS) 对于注解的描述如下:
  An annotation is a marker which associates information with a program construct, but has no effect at run time. An annotation denotes a specific invocation of an annotation type and usually provides values for the elements of that type.
  笔者感觉注解像是标签,用来标记一个对象,成员变量,方法等等。 Java中用@interface表示一个注解类型(Annotation Type),@和interface之间可以有空格但是不建议这样使用。
  注解被分为普通注解(Normal Annotation)、标记注解(Marker Annotations)和单元素注解(Single-Element Annotations)。

普通注释

  普通注解指定注解类型,并可选地指定用逗号分隔的键值对的列表。每对都包含与注解类型的元素相关联的值。相当于用key-value的形式赋值,每个key的名称都是注解类型在声明时候的方法名,而value要符合对应方法的返回值类型。

NormalAnnotation:
  @ TypeName ( [ElementValuePairList] )
ElementValuePairList://键值对list
  ElementValuePair {, ElementValuePair}
ElementValuePair://键值对
  Identifier = ElementValue
ElementValue://值
  ConditionalExpression //条件表达式
  ElementValueArrayInitializer //以数组形式初始化的array数组,例如[1,2,3]
  Annotation//注解
ElementValueArrayInitializer:
  { [ElementValueList] [,] }//array数组
ElementValueList:
  ElementValue {, ElementValue}//list列表

  上面是普通注解的例子,下面是普通注解类型声明的例子。

/**
 * Describes the "request-for-enhancement" (RFE)
 * that led to the presence of the annotated API element.
 */
@interface RequestForEnhancement {
    int    id();        // Unique ID number associated with RFE
    String synopsis();  // Synopsis of RFE
    String engineer();  // Name of engineer who implemented RFE
    String date();      // Date RFE was implemented
}

  接下来是普通注解类型的使用。

@RequestForEnhancement(
    id       = 2868724,
    synopsis = "Provide time-travel functionality",
    engineer = "Mr. Peabody",
    date     = "4/1/2004"
)
public static void travelThroughTime(Date destination) { ... }

标记注解

  标记注解是普通注解简化而来的,使用标记注解类型进行标记。

MarkerAnnotation:
  @ TypeName

  上面是标记注解的例子,下面是标记注解类型声明的例子。

/**
 * An annotation with this type indicates that the 
 * specification of the annotated API element is 
 * preliminary and subject to change.
 */
@interface Preliminary {}

  接下来是标记注解类型的使用。

@Preliminary 
public class TimeTravel { ... }

单元素注解

  单元素注解是普通注解简化而来的,使用单元素注解类型进行标记。

MarkerAnnotation:
  @ TypeName

  上面是单元素注解的例子,下面是单元素注解类型声明的例子。

/**
 * Associates a copyright notice with the annotated API element.
 */
@interface Copyright {
    String value();
}
/**
 * Associates a list of endorsers with the annotated class.
 */
@interface Endorsers {
    String[] value();
}
interface Formatter {}

// Designates a formatter to pretty-print the annotated class
@interface PrettyPrinter {
    Class<? extends Formatter> value();
}
/**
 * Indicates the author of the annotated program element.
 */
@interface Author {
    Name value();
}
/**
 * A person's name.  This annotation type is not designed
 * to be used directly to annotate program elements, but to
 * define elements of other annotation types.
 */
@interface Name {
    String first();
    String last();
}
@interface Quality {
    enum Level { BAD, INDIFFERENT, GOOD }
    Level value();
}

  一般情况下单元素注解类型的元素名称即方法名为value,而返回值的类型可以是基本数据类型、数组类型、指定子类类型、自定义注解类型和枚举类等。接下来是单元素注解类型的使用。

@Copyright("2002 Yoyodyne Propulsion Systems, Inc.")
public class OscillationOverthruster { ... }
@Endorsers({"Children", "Unscrupulous dentists"})
public class Lollipop { ... }
@Endorsers("Epicurus")
public class Pleasure { ... }
class GorgeousFormatter implements Formatter { ... }

@PrettyPrinter(GorgeousFormatter.class)
public class Petunia { ... }

// Illegal; String is not a subtype of Formatter
@PrettyPrinter(String.class)
public class Begonia { ... }
@Author(@Name(first = "Joe", last = "Hacker"))
public class BitTwiddle { ... }
@Quality(Quality.Level.GOOD)
public class Karma { ... }

2. 元注解

  元注解的作用是负责注解其他注解。Java5.0定义了4个标准的meta-annotation类型,分别为@Target、@Retention、@Documented和@Inherited。后来在Java8.0中添加了@Repeatable元注解。
  

@Target

  @Target用于指定一个注解类型的应用上下文。 @Target有只有一个元素用于指定上下文,元素名称为value,类型为java.lang.annotation.ElementType[]。如果注解类型A的声明中不存在java.lang.annotation.Target注解,则注解类型A适用于除类型参数声明之外的所有声明上下文,并且不适用于任何类型上下文(即不能用于Java8新添加的java.lang.annotation.ElementType.TYPE_PARAMETER和java.lang.annotation.ElementType.TYPE_USE对应的上下文)。
  以下的九种上下文每个都对应了java.lang.annotation.ElementType枚举类中的一个元素:
1. 模块声明
  对应java.lang.annotation.ElementType.MODULE,表示被注解的注解类型能用于模块声明。这个是Java9才有的。

2. 包声明
  对应java.lang.annotation.ElementType.PACKAGE,表示被注解的注解类型能用于包声明。
3. 类型声明: class, interface, enum, and annotation type declarations
  对应java.lang.annotation.ElementType.TYPE,表示被注解的注解类型能用于类型声明(除了注解类型)。
  相应的,如果要让被注解的注解类型A可以用于注解注解类型B的话(创建一个自定义元注解),应该用java.lang.annotation.ElementType.ANNOTATION_TYPE。
4. 方法声明 (包括注解类型的元素)
  对应java.lang.annotation.ElementType.METHOD,表示被注解的注解类型能用于方法声明。
5. 构造函数声明
  对应java.lang.annotation.ElementType.CONSTRUCTOR,表示被注解的注解类型能用于构造函数声明。
6. 泛型类、泛型接口、泛型方法和泛型构造函数的类型参数声明
  对应java.lang.annotation.ElementType.TYPE_PARAMETER,表示被注解的注解类型能用于泛型相关的类型参数声明。
7. 字段声明 (包括枚举常量)
  对应java.lang.annotation.ElementType.FIELD,表示被注解的注解类型能用于字段声明。
8. 形式参数和异常参数声明
  对应java.lang.annotation.ElementType.PARAMETER,表示被注解的注解类型能用于形式参数和异常参数声明。
9. 局部变量声明 (包括for 语句的循环变量和try-with-resources语句的资源变量)
  对应java.lang.annotation.ElementType.LOCAL_VARIABLE,表示被注解的注解类型能用于局部变量声明。
  还有以下16种使用了类型的上下文,都由枚举常量java.lang.annotation.ElementType.TYPE_USE表示。

  • 在声明中:
  1. 类声明的extends或implements子句中的类型。

  2. 接口声明的extends子句中的类型。

  3. 方法的返回类型(包括注释类型的元素的类型)

  4. 方法或构造函数的throws子句中的类型。

  5. 泛型类、接口、方法或构造函数的类型参数声明的extends子句中的类型。

  6. 类或接口的字段声明中的类型(包括枚举常量)。

  7. 方法、构造函数或lambda表达式的形式参数声明中的类型。

  8. 方法的接收器参数的类型。

  9. 局部变量声明中的类型。

  10. 异常参数声明中的类型。

  • 在表达式中:
  1. 显式类型参数列表中的类型,用于显式构造函数调用语句或类实例创建表达式或方法调用表达式。

  2. 在非限定类实例创建表达式中,作为要实例化的类类型或要实例化的匿名类的直接超类或直接超接口。

  3. 数组创建表达式中的元素类型。

  4. 转换表达式的强制转换运算符中的类型。

  5. 关系运算符实例后面的类型。

  6. 在方法引用表达式中,作为搜索成员方法的引用类型或要构造的类类型或数组类型。

@Retention

  @Retention用于指定注解类型的生存周期。

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Retention {
    /**
     * Returns the retention policy.
     * @return the retention policy
     */
    RetentionPolicy value();
}

  @Retention只有一个元素value,类型是java.lang.annotation.RetentionPolicy。

public enum RetentionPolicy {
    /**
     * 编译器将丢弃注解。
     */
    SOURCE,

    /**
     * 注解将由编译器记录在类文件中,但在运行时不需要由VM保留。这是默认行为。
     */
    CLASS,

    /**
     * 注解将由编译器记录在类文件中,并在运行时由VM保留,因此可以反射性地读取它们。
     * 详情参考java.lang.reflect.AnnotatedElement接口中的方法
     * @see java.lang.reflect.AnnotatedElement
     */
    RUNTIME
}

  RetentionPolicy类是一个枚举类,其中的元素有SOURCE、CLASS和RUNTIME。

@Documented

  @Documented表示将注解类型变成为带注解元素的公共API的一部分。被@Documented注解的注解类型会被javadoc和类似工具记录。

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Documented {
}

  @Documented是一个标记注解类型,没有元素。

@Inherited

  @Inherited注解的注解类型A,注解类型A注解的类C上的注解类型A会被C的子类继承。(注解类型java.lang.annotation.Inherited用于指示对应于给定注解类型的类C上的注解会被C的子类继承。)

@Repeatable

  @Repeatable注解的注解类型可以在同一个地方重复注解。(注解类型java.lang.annotation.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注解时的值也是个单元素注解类型。下面举个例子:
  我们需要一个Identity注解类型表示一个人的一个身份,但是一个人可以有多个身份。

  1. 创建一个Identities注解类型用来存放Identity注解类型。
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Identities {
    Identity[] value();
}
  1. 创建一个Identity注解类型用来存放身份信息。
@Repeatable(Identities.class)
@Retention(RetentionPolicy.RUNTIME)
public @interface Identity {
    String value() default "";
}
  1. 创建一个Person类。
public class Person {
    private List<String> identities = new ArrayList<>();

    public void addIdentity(String identity){
        this.identities.add(identity);
    }

    public String printIdentities() {
        System.out.println(identities.toString());
    }
}
  1. 使用Identity注解类型。
public class TestCase {

    @Identity("student")
    @Identity("son")
    @Identity("worker")
    public static Person hxl = new Person();

    public static void main(String[] args) {
        Class<TestCase> clz = TestCase.class;
        Field[] declaredFields = clz.getDeclaredFields();
        for (Field field : declaredFields){
            if (field.isAnnotationPresent(Identities.class)){
                Identities identities = field.getAnnotation(Identities.class);
                Identity[] identityList = identities.value();
                for (Identity identity : identityList){
                    hxl.addIdentity(identity.value());
                }
            }
        }
        hxl.printIdentities();
    }
}

  可以看出在反射时获得的注解类型是Identities.class(存放Identity注解类型的容器)。
  下面是运行结果:

3. 自定义注解

  下面是JLS对注解类型的定义:

AnnotationTypeDeclaration:
  {InterfaceModifier} @ interface TypeIdentifier AnnotationTypeBody

InterfaceModifier:
  (one of) 
  Annotation public protected private 
  abstract static strictfp

TypeIdentifier//注解类型名称

AnnotationTypeBody//注解类型定义体

  下面是JLS对AnnotationTypeBody的定义:

AnnotationTypeBody:
  { {AnnotationTypeMemberDeclaration} }

AnnotationTypeMemberDeclaration:
  AnnotationTypeElementDeclaration //注解类型的元素声明(方法声明)
  ConstantDeclaration //注解类型的常量声明
  ClassDeclaration //类声明
  InterfaceDeclaration //接口声明
  ;

AnnotationTypeElementDeclaration:
  {AnnotationTypeElementModifier} 返回类型 方法名( ) [Dims] [DefaultValue] ;

AnnotationTypeElementModifier:
  (one of) 
  Annotation public 
  abstract

Dims:
  {Annotation} [ ] {{Annotation} [ ]}

  注解类型声明中的方法声明不能具有形式参数,类型参数或throws子句。
  注解类型声明中的方法声明不能为default或static。因此,注解类型不能像普通接口类型一样声明各种方法。请注意,注解类型仍然可以从其隐式超接口java.lang.annotation.Annotation继承默认方法,但Java SE 11中不存在此类默认方法。
  在注解类型中声明的方法的返回类型必须是以下之一,否则会发生编译时错误:

  1. 基本数据类型
  2. String类型
  3. Class类型或者Class的调用
  4. enum类型
  5. annotation类型
  6. 以上所有类型的数组

  数组类型的返回值支持空的大括号“{}”。
  如果在注解类型中声明的任何方法具有等同于覆盖在类Object或接口java.lang.annotation.Annotation中声明的任何公共或受保护方法的签名,则编译时会报错。
  如果注解类型声明T直接或间接包含类型为T的元素,则编译时会报错。

ps:如果有疑问或者是我写的有错的地方欢迎指出~^_^~

参考文献: