在 Java 8 之前的版本中,只能允许在声明式前使用 Annotation。而在 Java 8 版本中,Annotation 可以被用在任何使用 Type 的地方,例如:初始化对象时 (new),对象类型转化时,使用 implements 表达式时,或者使用 throws 表达式时。
//初始化对象时
String myString = new @NotNull String();
//对象类型转化时
myString = (@NonNull String) str;
//使用 implements 表达式时
class MyList implements @ReadOnly List<@ReadOnly T>{
...
}
//使用 throws 表达式时
public void validateValues() throws @Critical ValidationFailedException{
...
}
定义一个 Type Annotation 的方法与普通的 Annotation 类似,只需要指定 Target 为 ElementType.TYPE_PARAMETER 或者 ElementType.TYPE_USE,或者同时指定这两个 Target。
@Target({ElementType.TYPE_PARAMETER, ElementType.TYPE_USE})
public @interface MyAnnotation {
}
ElementType.TYPE_PARAMETER 表示这个 Annotation 可以用在 Type 的声明式前,而 ElementType.TYPE_USE 表示这个 Annotation 可以用在所有使用 Type 的地方(如:泛型,类型转换等)
与 Java 8 之前的 Annotation 类似的是,Type Annotation 也可以通过设置 Retention 在编译后保留在 class 文件中(RetentionPolicy.CLASS)或者运行时可访问(RetentionPolicy.RUNTIME)。但是与之前不同的是,Type Annotation 有两个新的特性:在本地变量上的 Annotation 可以保留在 class 文件中,以及泛型类型可以被保留甚至在运行时被访问。
虽然 Type Annotation 可以保留在 class 文件中,但是它并不会改变程序代码本身的行为。例如在一个方法前加上 Annotation,调用此方法返回的结果和不加 Annotation 的时候一致。
Java 8 通过引入 Type Annotation,使得开发者可以在更多的地方使用 Annotation,从而能够更全面地对代码进行分析以及进行更强的类型检查。
@Target({ ElementType.FIELD })
@Retention(RetentionPolicy.RUNTIME)
public @interface PrimeNumber1 {
}
@Target({ ElementType.TYPE_USE })
@Retention(RetentionPolicy.RUNTIME)
public @interface PrimeNumber2 {
}
@Target({ ElementType.TYPE_USE })
@Retention(RetentionPolicy.RUNTIME)
public @interface PrimeNumber3 {
}
定义如上三个注解,则在一个类中定义一个域时,可以有如下这种方式;
@PrimeNumber1
private @PrimeNumber2 String anInt = new @PrimeNumber3 String();
其中在PrimeNumber1和PrimeNumber2 这两个位置是一样的效果,这个一样的效果是说,同一个注解放在PrimeNumber1处还是PrimeNumber2处,作用是一样的,用Java Reflect API的时候(Field.getAnnotations()或者Field.getAnnotatedType().getAnnotations()),注解放在这两个位置都能获取到;
Field.getAnnotations()是用来获取Target为ElementType.FIELD的注解;
Field.getAnnotatedType().getAnnotations()用来获取Target为ElementType.TYPE_USE的注解;
public class GetAnnotatedTypeExample2<@PrimeNumber4 T> {
public static void main(String... args) throws NoSuchFieldException {
TypeVariable>[] tv = GetAnnotatedTypeExample2.class.getTypeParameters();
System.out.println(tv[0].getAnnotations()[0].toString());
}
}
@Target({ ElementType.TYPE_PARAMETER })
@Retention(RetentionPolicy.RUNTIME)
public @interface PrimeNumber4 {
}
类型注解被用来支持在Java的程序中做强类型检查。配合第三方插件工具Checker Framework(注:此插件so easy,这里不介绍了),可以在编译的时候检测出runtime error(eg:UnsupportedOperationException; NumberFormatException;NullPointerException异常等都是runtime error),以提高代码质量。这就是类型注解的作用。
Note:
使用Checker Framework可以找到类型注解出现的地方并检查。
eg:
import checkers.nullness.quals.*;
public class TestDemo{
void sample() {
@NonNull Object my = new Object();
}
}
使用javac编译上面的类:(当然若下载了Checker Framework插件就不需要这么麻烦了)
javac -processor checkers.nullness.NullnessChecker TestDemo.java
上面编译是通过的,但若修改代码:
@NonNull Object my = null;
但若不想使用类型注解检测出来错误,则不需要processor,正常javac TestDemo.java是可以通过编译的,但是运行时会报 NullPointerException 异常。
为了能在编译期间就自动检查出这类异常,可以通过类型注解结合 Checker Framework 提前排查出来错误异常。
注意java 5,6,7版本是不支持注解@NonNull,但checker framework 有个向下兼容的解决方案,就是将类型注解@NonNull 用/**/注释起来。
import checkers.nullness.quals.*;
public class TestDemo{
void sample() {
/*@NonNull*/ Object my = null;
}
}
这样javac编译器就会忽略掉注释块,但用checker framework里面的javac编译器同样能够检测出@NonNull错误。
通过 类型注解 + checker framework 可以在编译时就找到runtime error。
参考
Java 8 Annotation 新特性在软件质量和开发效率方面的提升
jdk8的扩展注解
The Checker Framework