从JDK 5 开始,Java 增加了对元数据(MetaData)的支持,也就是Annotation(注解),注解可以理解为代码里的特殊标记,这些标记可以在编译、类加载、运行时被读取,并执行相应的处理

注解能被用来为程序元素(类、方法、成员变量)设置元数据。如果希望注解在运行时起到一定的作用,只有通过某种配套的工具对注解中的信息进行访问和处理,访问和处理注解的工具统称 APT(Annotation Processing Tool)

基本注解

@Override

@Override 就是用来指定方法重写的,它可以强制一个子类必须重写父类的方法。@Override 主要是帮助程序员避免一些低级错误(例如拼写错误等)

public class Fruit {
    public void info(){
        System.out.println("水果");
    }
}
public class Apple extends Fruit{
    // 编译报错 父类是info() 这里是Info()
    @Override
    public void Info() {
        super.info();
    }
}

@Override 只能修饰方法

@Deprecated

用于表示某个程序元素(类,方法等)已过时,当其他程序使用已过时的类、方法时,编译器将会给出警告

public class Apple extends Fruit{
    @Deprecated
    public void info() {
        super.info();
    }

    public static void main(String[] args) {
        Apple apple = new Apple();
        // 下方代码 编译器会警告方法已过时
        apple.info();
    }
}

@SuppressWarnings

被@ SuppressWarnings 注解修饰的程序元素取消显示指定的编译器警告

@SuppressWarnings(value = "unchecked")
public class Main {
    public static void main(String[] args) {
        List myList = new ArrayList<Integer>();
        List<String> ls = myList; // 执行了未检查的转换,unchecked警告
    }
}

@FunctionalInterface

如果接口中只有一个抽象方法,该接口就是函数式接口。@FunctionalInterface 就是用来指定某个接口必须是函数式接口

@FunctionalInterface
public interface Converter {
    Integer convert(String from);
}
 Converter converter = from -> Integer.valueOf(from);

自定义注解

定义注解

定义新的注解类型使用 @interface 关键字,并且可以携带成员变量,一旦注解里定义了成员变量之后,使用该注解时就应该为它的成员变量指定值,也可以在定义注解的成员变量时为其指定初始值(默认值)

public @interface MyTag {
    String name();
    int age() default 18;
    String set() default "男";
}
public class Test {
    @MyTag(name = "张三",age = 20)
    public void info(){
        
    }
}

修饰注解

java.lang.annotation 包下提供了6个注解,其中有5个用于修饰注解,下面介绍常用的4个注解

@Retention

@Retention 用于指定被修饰的注解可以保留多长时间,@Retention 包含一个 RetentionPolicy类型value 成员变量,因此使用 @Retention 时必须为该 value 成员指定值

value 成员变量的值有如下三个

  • RetentionPolicy.CLASS 编译器将把注解记录在 class 文件中。 当运行Java 程序时,JVM 不可获取注解信息(默认)
  • RetentionPolicy.RUNTIME 编译器将把注解记录在 class 文件中。 当运行Java 程序时,JVM 可获取注解信息
  • RetentionPolicy.SOURCE 注解只保留在源代码中,编译器直接丢弃这种注解

@Target

@Target注解用于被它修饰的注解能用于修饰哪些程序单元,该注解也包含一个名为value 的成员变量,该成员变量只能是如下几个

  • ElementType.ANNOTATION_TYPE 指定该策略的注解只能修饰注解
  • ElementType.CONSTRUCTOR 指定该策略的注解只能修饰构造器
  • ElementType.FIELD 指定该策略的注解只能修饰成员变量
  • ElementType.LOCAL_VARIABLE 指定该策略的注解只能修饰局部变量
  • ElementType.METHOD 指定该策略的注解只能修饰方法
  • ElementType.PACKAGE 指定该策略的注解只能修饰包
  • ElementType.PARAMETER 指定该策略的注解只能修饰参数
  • ElementType.TYPE 指定该策略的注解只能修饰类、接口(或者注解类型)或枚举定义

@Documented

@Documented 用于指定被改注解修饰的注解类将被 javadoc 工具提取成文档,如果定义注解类是使用了 @Documented 修饰,那么所有使用该注解修饰的程序元素 的API 文档中将会包含该注解生命

@Inherited

@Inherited 元注解指定被它修饰的注解将具有继承性——如果某个类使用了@Xxx 注解修饰,则其子类将自动被@Xxx 修饰

提取注解信息

使用注解修饰了类、方法、成员变量等成员后,这些注解不会自动生效,需要由开发者提供相应的代码来提取并处理注解信息

Java 使用java.lang.annotation.Annotation 接口来代表程序元素前面的注解,该接口是所有注解的父接口。 Java 5 在java.lang.reflect 包下新增了AnnotatedElement 接口,该接口代表程序中可以接受注解的程序元素。 该接口主要有如下几个实现类

  • Class 类定义
  • Constructor 构造器定义
  • Field 类的成员变量定义
  • Method 类的方法定义
  • Package 类的包定义

当程序通过反射获取到某个类的 AnnotatedElement 对象后,程序就可以调用该对象的如下几个方法来访问注解信息

  • <A extends Annotation> A getAnnotation(Class<A> annotationClass) 返回该程序元素上存在的、指定类型的注解,如果该类型注解不存在,则返回 null
  • <A extends Annotation> A getDeclaredAnnotation(Class<A> annotationClass) 该方法尝试获取直接修饰该程序元素、指定类型的注解。如果该类型注解不存在,则返回 null
  • Annotation[] getAnnotations() 返回该程序元素上存在的所有注解
  • Annotation[] getDeclaredAnnotations() 获取直接修饰该程序元素的所有注解
  • boolean isAnnotationPresent(Class<? extends Annotation> annotationClass) 判断该程序元素上是否存在指定类型的注解
  • <A extends Annotation> A[] getAnnotationsByType(Class<A> annotationClass) 获取修饰该程序元素、指定类型的多个注解
  • <A extends Annotation> A[] getDeclaredAnnotationsByType(Class<A> annotationClass) 获取直接修饰该程序元素、指定类型的多个注解
@Retention(value = RetentionPolicy.RUNTIME)
public @interface MyTag {
    String name();

    int age() default 18;

    String set() default "男";
}
public class Test {
    @MyTag(name = "张三", age = 20)
    public void info() {

    }

    public static void main(String[] args) throws Exception {
        Test test = new Test();
        // 获取info 方法的所有注解
        Annotation[] annotations = test.getClass().getMethod("info").getAnnotations();
        for (Annotation annotation : annotations) {
            // 如果该注解时MyTag类型
            if (annotation instanceof MyTag) {
                // 强转为MyTag
                MyTag tag = (MyTag) annotation;
                // 输出MyTag的成员变量的值
                System.out.println("tag.name:" + tag.name());
                System.out.println("tag.age:" + tag.age());
            }
        }
    }
}

输出

tag.name:张三
tag.age:20