Java注解详解

文章目录

  • 参考资料
  • 源码地址
  • 概述
    • Annotation
    • 元素
      • 默认值
    • 用途
  • 元注解
    • @Documented
    • @Target
    • @Retention
    • @Inherited
    • @Override
    • @SuppressWarnings
      • 抑制警告的关键字
    • @Deprecated
    • @SafeVarargs
    • @FunctionalInterface
  • @Repeatable
  • 检索注解

参考资料

  1. Java中的注解是如何工作的?
  2. Java基础加强总结(一)——注解(Annotation)
  3. Java注解教程
  4. Java Annotation认知(包括框架图、详细介绍、示例说明)
  5. Java Language Specification - Chapter 9. Interfaces
  6. Java魔法堂:注解用法详解——@SuppressWarnings

源码地址

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中,声明了两个元素nameversion,它们都是通过方法声明的。

值的注意的是:

  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,中声明了两个元素nameversion,其中,name指定了默认值tea。当使用C时,可以不对元素name赋值。当未对元素version赋值时,IDE会报错,提示"version是必要的但是没有找到",也就意味着未指定默认值的元素,必须在使用时对该元素赋值

Java注解详解_第1张图片

用途

注解有多种用途,包括:

  • 向编译器提供信息 —— 编译器使用注解检查错误或忽略警告
  • 编译时和部署时处理 —— 注解处理工具可以处理注解信息来生成代码,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会给出错误提示,如下:

Java注解详解_第2张图片

@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);
}
  1. 抑制多类型的警告
@SuppressWarnings(value={"unchecked", "rawtypes"})
public void addItems(String item){
   List items = new ArrayList();
   items.add(item);
}
  1. 抑制所有类型的警告
@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会给出警告提示,如下所示:

Java注解详解_第3张图片

@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方法(默认方法)需要返回其元注解(即可重复注解类)的列表,否则这个容器类创建的不符合需求。
  1. 声明可重复注解类型 - E
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD, ElementType.TYPE})
@Repeatable(Es.class)
public @interface E {
	String value();
}

可重复注解与普通注解没有太大区别,值得注意的是,使用@Repeatable表示其可以重复注解元素,还要指定可重复注解类型的容器,比如这里指定的是Es.class

  1. 使用可重复注解
@E("A")
@E("B")
@E("C")
@E("D")
public class EE {
	
}

使用E注解EE,共重复了4次,其value值分别为A,B,C,D。

  1. 获取重复注解
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注解详解_第4张图片

检索注解

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具有RuntimeVisibleAnnotationsRuntimeVisibleParameterAnnotationsRuntimeVisibleTypeAnnotations属性,并且该属性包含A,则注解A直接存在元素E上。
  • 间接存在 - 如果E具有RuntimeVisibleAnnotationsRuntimeVisibleParameterAnnotationsRuntimeVisibleTypeAnnotations属性,并且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);
        }
    }
}

你可能感兴趣的:(注解,Annotation,Java)