浅析Java中的注解

121 阅读7分钟

注解

在这里插入图片描述

1. 概念

注解(Annotation),也叫元数据。一种代码级别的说明。它是JDK1.5及以后版本引入的一个特性,与类、接口、枚举是在同一个层次。它可以声明在包、类、字段、方法、局部变量、方法参数等的前面,用来对这些元素进行说明,注释。

注解主要有如下三个作用:

  • 编写文档:通过代码里标识的元数据生成文档,如使用javadoc命令生成文档doc文档
  • 代码分析:通过代码里标识的元数据对代码进行分析,如使用反射
  • 编译检查:通过代码里标识的元数据让编译器能够实现基本的编译检查,如@Override

简单来说,注解就是给计算机看的一种用于说明程序的东西,主要是起到一种标识的作用。

2. 分类

按运行机制分类:

  • 源码注解
  • 编译时注解
  • 运行时注解

按来源分类:

  • Java内置注解
  • 第三方注解
  • 自定义注解

3. Java内置注解

Java本身定义了7个注解,其中@Override@Deprecated@SuppressWarnings位于java.lang中,下面称为内置注解;另外4个位于java.lang.annotation中,将其称为元注解。

2.1 内置注解

  • @Override:这个注解在前面类的使用中已经见过很多次了,例如,如果想要重写父类中定义的方法,子类重写的方法上就可以添加@Override;以及任何类重写Object类中的toString()equals()等方法时,IDEA都会自动的在方法上添加注解。因此,它主要用于编译时检查添加注解的方法是否是重写方法,当它的父类或实现的接口中没有此方法时,编译就会自动报错

    @Target(ElementType.METHOD)
    @Retention(RetentionPolicy.SOURCE)
    public @interface Override {
    }
    
  • @Deprecated:它用于标记过时的方法,注意标记为过时的方法,只是说它已经有了更好替代,方法仍可用只是不推荐。例如Date类中就存在大量被@Deprecated标记的方法,因为Calendar类中有其对应的替代方法。

    @Documented
    @Retention(RetentionPolicy.RUNTIME)
    @Target(value={CONSTRUCTOR, FIELD, LOCAL_VARIABLE, METHOD, PACKAGE, PARAMETER, TYPE})
    public @interface Deprecated {
    }
    
  • @SuppressWarnings:它用于指示编译器去忽略注解中声明的警告。例如,当类中定义的方法没有使用时,编辑器会自动显式警告,如果想要忽略警告信息们可以使用该注解

    @Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE})
    @Retention(RetentionPolicy.SOURCE)
    public @interface SuppressWarnings {
        String[] value();
    }
    

使用示例:

@SuppressWarnings("all")
public class AnnotationTest {
    @Override
    public String toString() {
        return "AnnotationTest{}";
    }

    @Deprecated
    public void show(){
    }
	
	// show()的更新版
    public void newShow(){
    }
}

2.2 元注解

元注解和所有的元xx含义是类似的,它就是用来注解其他注解的注解。Java内置了如下4个元注解:

  • @Retention:它用来标识注解应该保存于Java程序三个阶段的哪一个中,只在源代码中、写入.class文件中还是运行时通过反射访问。

    @Retention的源码实现为:

    package java.lang.annotation;
    
    @Documented
    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.ANNOTATION_TYPE)
    public @interface Retention {
        RetentionPolicy value();
    }
    

    它只有一个成员变量value(),类型为RetentionPolicy。我们继续看一下RetentionPolicy的源码:

    package java.lang.annotation;
    
    public enum RetentionPolicy {
        // Annotation信息仅存在于编译器处理期间,编译器处理完之后就没有该Annotation信息
        SOURCE,
        // 编译器将Annotation存储于类对应的.class文件中。默认行为
        CLASS,
        // 编译器将Annotation存储于class文件中,并且在运行时可由JVM读入
        RUNTIME
    }
    

    从源码中可以看出,RetentionPolicy是一个枚举(enum)类型,其中包含有SOURCECLASSRUNTIME三个值,它们分别对应了Java程序的三个阶段

  • @Documented:它用来标记注解是否应包含到文档中,主要用于使用javadoc生成文档时是否会显示注解

    注解的源码实现为:

    package java.lang.annotation;
    
    @Documented
    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.ANNOTATION_TYPE)
    public @interface Documented {
    }
    

    它本身没有包含成员变量,一般在使用中它可有可无,只有在有需要生成javadoc文档时有用。

  • @Target:它用来标记注解应是那种Java成员,注解的源码实现如下:

    package java.lang.annotation;
    
    @Documented
    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.ANNOTATION_TYPE)
    public @interface Target {
        ElementType[] value();
    }
    

    可以看到@Target中只有一个类型为ElementType[]类型的成员变量value(),接下来再看一下ElementType的源码:

    public enum ElementType {
        TYPE,
    
        FIELD,
    
        METHOD,
    
        PARAMETER,
    
        CONSTRUCTOR,
    
        LOCAL_VARIABLE,
    
        ANNOTATION_TYPE,
    
        PACKAGE,
    
        TYPE_PARAMETER,
    
        TYPE_USE
    }
    

    ElementType同样是一个枚举类型,其中包含10个值,它们分别用于标记注解能用来修饰什么类型的成员:

    • TYPE:类、接口或枚举声明
    • FIELD:字段声明
    • METHOD:方法声明
    • PARAMETER:参数生命
    • CONSTRUCTOR:构造方法声明
    • LOCAL_VARIABLE:局部变量声明
    • ANNOTATION_TYPE:注解类型声明
    • PACKAGE:包声明
    • TYPE_PARAMETER:Type参数声明
    • TYPE_USE:类型使用声明
  • @Inherited:它用来标记注解是继承自哪个注解类,源码实现为:

    package java.lang.annotation;
    
    @Documented
    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.ANNOTATION_TYPE)
    public @interface Inherited {
    }
    

    @Inherited的实现中同样没有成员变量,只是起来一个简单的标识作用。

4. 自定义注解

从前面Java内置注解的源码实现中,我们对于注解的定义有了初步的感受。如果用户想要自定义注解,注解的通用定义一般如下所示:

@Documented
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotation {
	// 成员变量
}

注意事项:

  • @interface是定义注解的关键字,当使用它定义注解时,表示该注解实现了java.lang.annotation.Annotation接口,接口的实现由编译器完成,同时该注解不能再继承其他注解或接口

  • 成员变量的类型是有限的,合法的类型包括:

    • 八大基本数据类型
    • String
    • 枚举
    • 注解
    • 以上类型的数组
  • 当注解中只有一个成员变量时,变量名必须是value(),注解使用时可以忽略成员名和=

  • 没有成员变量的注解称为标识注解

  • 可以使用default关键字给成员变量设置默认初始值,使用注解时就可以不对成员变量赋值

  • 注解使用时,如果成员变量是数组,那么赋值时使用{}包含,如果只有一个值,则可忽略{}

假设我们定义一个注解@Description,它只有一个String类型的成员变量value()

import java.lang.annotation.*;

@Target({ElementType.METHOD,ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface Description {
    String value();
}

然后定义一个接口,接口中有一些抽象方法

public interface People {
    String name();
    int age();
    void work();
}

接着定义接口的实现类,并在类中使用自定义的注解

@Description("class annotation...")
public class Employee implements People{
    @Override
    public String name() {
        return null;
    }

    @Override
    @Description("method annotation...")
    public int age() {
        return 0;
    }

    @Override
    public void work() {

    }
}

由于注解中只有一个String类型的成员变量,因此这里简单的传入一个字符串,用来标识该注解用在哪里。定义好类之后,我们需要创建一个包含main()的类来使用Employee,定义如下:

import java.lang.reflect.Method;

public class AnnotationDemo {
    public static void main(String[] args) throws Exception{
        // 获取Class类对象
        Class<?> aClass = Class.forName("Annotation.Employee");
        // 判断类是否使用了注解
        if (aClass.isAnnotationPresent(Description.class)){
            // 获取使用的注解实例
            Description annotation = aClass.getAnnotation(Description.class);
            System.out.println(annotation.value());  //class annotation...
        }
		
        // 获取类对象的成员方法
        Method[] methods = aClass.getMethods();
        for (Method method : methods) {
            // 判断方法是否使用了注解
            if (method.isAnnotationPresent(Description.class)){
                // 获取使用的注解实例
                Description annotation = method.getAnnotation(Description.class);
                System.out.println(annotation.value());  // method annotation...
            }
        }
    }
}

5. 使用案例

在前面反射的部分,我们使用了反射来运行配置文件内容,同样的功能也可以使用今天学习的注解来实现。首先定义Porp注解,其中包含className()methodName()两个String类型的成员变量,它和前面的配置文件是对应的。

import java.lang.annotation.*;

@Target({ElementType.METHOD,ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface Prop {
    String className();
    String methodName();
}

然后使用同样的Person类:

package Annotation;

public class Person {
    private String name;
    private int age;

    public Person() {
    }

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public String getSchool() {
        return school;
    }

    public void setSchool(String school) {
        this.school = school;
    }

    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", school='" + school + '\'' +
                '}';
    }

    public void say(){
        System.out.println("say hello...");
    }
}

最后编写测试类:

import java.lang.reflect.Method;

@Prop(className = "Annotation.Person", methodName = "say")
public class PropDemo {
    public static void main(String[] args) throws Exception {
        // 获取Class类对象
        Class<PropDemo> c = PropDemo.class;
        Prop annotation = c.getAnnotation(Prop.class);
        String className = annotation.className();
        String methodName = annotation.methodName();

        Class<?> aClass = Class.forName(className);
        // 获取方法对象
        Method method = aClass.getMethod(methodName);
        // 创建对象
        Object o = aClass.newInstance();
        // 执行方法
        method.invoke(o); // say hello...

    }
}

可以看出使用注解不仅可以实现同样的功能,而且实现更加的优雅,代码书写更简洁。

6. 参考

框架开发之Java注解的妙用

Java 注解(Annotation)