1. 首页
  2. Java
  3. 注解

Java注解基本介绍 二

上篇文章我们分析了两种元注解,@Target和@Retention

除了这两种元注解,Java中还提供了另外三种元注解,@Documented、@Inherited和@Repeatable。

下面我们继续分析这三个元注解:

@Documented,被该注解修饰的元素会生成到javadoc中

@DocumentedTest1
@DocumentedTest2
public class Student {
    @Deprecated  //Java内置注解,用于标识方法过期不建议使用
    @SuppressWarnings("uncheck") //忽略警告标识
    public static void read() {
    }
    @Test  // 使用@Test注解修饰的方法
    public void write() { read(); }
}
@Documented
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface DocumentedTest1 {
}
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface DocumentedTest2 {
}

使用javadoc命令生成doc文档

javadoc DocumentedTest1.java DocumentedTest2.java Student.java

效果图如下,可以看到@DocumentedTest1注解被生成到了Student类上

documented

使用了@Documented元注解定义的注解生成了JavaDoc文档,没有使用@Documented元注解的注解则没有生成doc文档,这个就是元注解的作用

@Inherited元注解,可以让注解被继承,但不是真的继承,只是通过使用@Inherited,可以让子类Class对象使用getAnnotations()获取父类被@Inherited修饰的注解,下面是一个简单的例子:

我们在上面的DocumentedTest1注解中添加元注解@Inherited

@Inherited
@Documented
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface DocumentedTest1 {
}
@DocumentedTest1
class A { }
class B extends A { }
@DocumentedTest2
class C { }
class D extends C { }
public class DocumentDemo {
    public static void main(String[] args) {
        A testA = new B();
        System.out.println(Arrays.toString(testA.getClass().getAnnotations()));
        C testC = new D();
        System.out.println(Arrays.toString(testC.getClass().getAnnotations()));
    }
}
/**
 * 运行结果:
 *  [@com.craig.DocumentedTest1()]
 *  []
 */

注解与反射机制

前面经过反编译后,我们知道Java所有注解都继承了Annotation接口,也就是说Java使用Annotation接口代表注解元素,该接口是所有Annotation类型的父接口。

同时为了运行时能准确获取到注解的相关信息,Java在java.lang.reflect反射包下新增了AnnotatedElement接口,它主要用于表示目前正在JVM中运行的程序中已使用注解的元素,通过该接口提供的方法可以利用反射技术动态地读取注解的信息

如反射包的Constructor类、Field类、Method类、Package类和Class类都实现了AnnotatedElement接口,它们的含义如下:

Class:类的Class对象定义
Constructor:代表类的构造器定义
Field:代表类的成员变量定义
Method:代表类的方法定义
Package:代表类的包定义

下面是AnnotatedElement中相关的API方法,以上5个类都实现以下的方法

getAnnotation(Class<A> annotationClass) 该元素如果存在指定类型的注解,则返回这些注解,否则返回 null

getAnnotations() 返回此元素上存在的所有注解,包括从父类继承的

isAnnotationPresent(Class<? extends Annotation> annotationClass) 如果指定类型的注解存在于此元素上,则返回 true,否则返回 false

getDeclaredAnnotations() 返回直接存在于此元素上的所有注解,注意,不包括父类的注解,调用者可以随意修改返回的数组;这不会对其他调用者返回的数组产生任何影响,没有则返回长度为0的数组

@DocumentedTest1
class TestA { }
@DocumentedTest2
public class DocumentTest extends TestA{
    public static void main(String[] args) {
        try {
            //1、使用类加载器加载类
            Class clazz = Class.forName("com.craig.DocumentTest");
            //Class<?> clazz1 = Student.class;

            // 获取类上的注解有以下几种方式
            // 获取类上的注解不包含继承
            Annotation[] annotations1 = clazz.getDeclaredAnnotations();
            // 获取类上的所有注解,包含继承父类的注解
            Annotation[] annotations2 = clazz.getAnnotations();
            // 获取类上指定类型的注解
            Annotation annotation1 = clazz.getAnnotation(DocumentedTest1.class);

            // 2、类上面是否存在DocumentedTest的注解
            boolean isExist = clazz.isAnnotationPresent(DocumentedTest1.class);
            if (isExist) {
                // 3、存在则获取注解实例
                DocumentedTest1 documentedTest1 = (DocumentedTest1) clazz.getAnnotation(DocumentedTest1.class);
                System.out.println(documentedTest1.toString());
            }
            // 获取DocumentTest类的所有方法并遍历它们
            Method[] methods = clazz.getMethods();
            for (Method method : methods) {
                // 方法上是否存在指定类型注解
                boolean isMethodExist = method.isAnnotationPresent(DocumentedTest1.class);
                if (isMethodExist) {
                    // 存在则获取该注解
                    DocumentedTest1 annotation = method.getAnnotation(DocumentedTest1.class);
                    System.out.println(annotation.toString());
                }
            }
            for (Method method : methods) {
                // 获取方法上所有的注解并遍历这些注解
                Annotation[] annotations = method.getAnnotations();
                for (Annotation annotation : annotations) {
                    // 获取类型匹配的注解
                    if (annotation instanceof DocumentedTest1) {
                        DocumentedTest1 description = (DocumentedTest1) annotation;
                        System.out.println(description.toString());
                    }
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

java8中新增的元注解@Repeatable

元注解@Repeatable是JDK1.8新加入的,它表示在同一个位置重复相同的注解。在没有该注解前,一般是无法在同一个类型上使用相同的注解的

//Java8前无法这样使用
@FilterPath("/web/update")
@FilterPath("/web/add")
public class A {}

如果想要实现类似的功能,需要定义参数类型为数组来接收。但是在Java8之后使用这个注解就可以解决

@Target({ElementType.TYPE,ElementType.FIELD,ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Inherited //添加可继承元注解
@Repeatable(FilterPaths.class) // 参数指定接收的class
public @interface FilterPath{
    String  value();
}

@Target(ElementType.TYPE)
@Inherited //添加可继承元注解
@Retention(RetentionPolicy.RUNTIME)
@interface FilterPaths {
    FilterPath[] value();
}

@FilterPath("/test1/1")
@FilterPath("/test1/2")
@FilterPath("/test1/3")
class Test1{ }

可以简单理解为通过使用@Repeatable后,将使用@FilterPaths注解作为接收同一个类型上重复注解的容器,而每个@FilterPath则负责保存指定的路径串。为了处理上述的新增注解,Java8还在AnnotatedElement接口新增了getDeclaredAnnotationsByType() 和 getAnnotationsByType()两个方法并在接口给出了默认实现,在指定@Repeatable的注解时,可以通过这两个方法获取到注解相关信息。

这里我们需要注意的是旧版API中的getDeclaredAnnotation()和 getAnnotation()是不对@Repeatable注解的处理的(除非该注解没有在同一个声明上重复出现)。注意getDeclaredAnnotationsByType方法获取到的注解不包括父类,其实当 getAnnotationsByType()方法调用时,其内部先执行了getDeclaredAnnotationsByType方法,只有当前类不存在指定注解时,getAnnotationsByType()才会继续从其父类寻找,但请注意如果@FilterPath和@FilterPaths没有使用了@Inherited的话,仍然无法获取。

下面是示例代码:

import java.lang.annotation.*;

@Target({ElementType.TYPE,ElementType.FIELD,ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Inherited //添加可继承元注解
@Repeatable(FilterPaths.class) // 参数指定接收的class
public @interface FilterPath{
    String  value();
}

@Target(ElementType.TYPE)
@Inherited //添加可继承元注解
@Retention(RetentionPolicy.RUNTIME)
@interface FilterPaths {
    FilterPath[] value();
}

@FilterPath("/test1/1")
@FilterPath("/test1/2")
@FilterPath("/test1/3")
class Test1{ }

//只有在子类没有使用@FilterPath注解的时候,getAnnotationsByType()方法才会从父类中找@FilterPath,反之则不会
//@FilterPath("/test2/1")
//@FilterPath("/test2/2")
//@FilterPath("/test2/3")
class Test2 extends Test1 {
    public static void main(String[] args) {
        Class<?> clazz = Test2.class;
        //通过getAnnotationsByType方法获取所有重复注解
        FilterPath[] annotationsByType = clazz.getAnnotationsByType(FilterPath.class);
        FilterPath[] annotationsByType2 = clazz.getDeclaredAnnotationsByType(FilterPath.class);
        if (annotationsByType != null) {
            for (FilterPath filter : annotationsByType) {
                System.out.println("1:"+filter.value());
            }
        }
        System.out.println("-----------------");
        if (annotationsByType2 != null) {
            for (FilterPath filter : annotationsByType2) {
                System.out.println("2:"+filter.value());
            }
        }
        System.out.println("使用getAnnotation的结果:"+clazz.getAnnotation(FilterPath.class));

    }
}
 /**
  * 运行结果如下:
  *
  * 1:/test1/1
  * 1:/test1/2
  * 1:/test1/3
  * -----------------
  * 使用getAnnotation的结果:null
  */

Java 8 新增的注解类型

在Java 8中 ElementType 新增两个枚举成员,TYPE_PARAMETER 和 TYPE_USE ,在Java 8前注解只能标注在一个声明(如字段、类、方法)上,Java 8后,新增的TYPE_PARAMETER可以用于标注类型参数,而TYPE_USE则可以用于标注任意类型(不包括class)

如下所示

//TYPE_PARAMETER 标注在类型参数上 
class D<@Parameter T> { } 

//TYPE_USE则可以用于标注任意类型(不包括class) 
//用于父类或者接口 
class Image implements @Rectangular Shape { } 

//用于构造函数 
new @Path String("/usr/bin") 

//用于强制转换和instanceof检查,注意这些注解中用于外部工具,它们不会对类型转换或者instanceof的检查行为带来任何影响。 
String path=(@Path String)input; if(input instanceof @Path String) 

//用于指定异常 
public Person read() throws @Localized IOException. 

//用于通配符绑定
List<@ReadOnly ? extends Person> 
List<? extends @ReadOnly Person> 

@NotNull String.class 
//非法,不能标注

class import java.lang.@NotNull String 
//非法,不能标注import

这里主要说明一下TYPE_USE,类型注解用来支持在Java的程序中做强类型检查,配合第三方插件工具,可以在编译期检测出runtime error(如UnsupportedOperationException、NullPointerException异常),避免异常延续到运行期才发现,从而提高代码质量,这就是类型注解的主要作用。

总结,Java 8中新增加了两个注解的元素类型ElementType.TYPE_USE 和ElementType.TYPE_PARAMETER ,通过它们,我们可以把注解应用到各种新场合中。

发布者:Craig,转转请注明出处:https://www.goodlymoon.com/archives/1586.html

发表评论

电子邮件地址不会被公开。 必填项已用*标注

QR code