类型注解与重复注解一些限制
类型注解
原来注解只能用于声明,从JDK 8开始,注解还可以用于大多数使用类型的地方,这种注解称为类型注解。类型注解允许工具对代码执行额外的检查,从而帮助避免错误。javac本身一般不执行这些检查,这种工具需要作为编译器插件发挥作用。
例子
@Target(ElementType.TYPE_USE)
public @interface TypeAnno {
}
@Target(ElementType.TYPE_USE)
public @interface MaxLen {
int value();
}
@Target(ElementType.TYPE_USE)
public @interface Unique {
}
@Target(ElementType.TYPE_PARAMETER)
public @interface What {
String description();
}
@Target(ElementType.FIELD)
public @interface NotNull {
}
@Target(ElementType.METHOD)
public @interface Recommended {
}
// 注解类型参数
public class TypeTest<@What(description = "Generic data type") T> {
// 注解字段类型
private @TypeAnno String first;
// 注解字段声明
private @NotNull String second;
// 注解数组元素类型
private @TypeAnno Integer[] nums;
// 注解数组级别
private String @MaxLen(5) [] @MaxLen(10) [] data;
// 注解构造函数
public @Unique TypeTest() {
}
// 注解this的类型
public int f1(@TypeAnno TypeTest<T> this, int x) {
return 10;
}
// 注解方法返回类型
public @TypeAnno Integer f2(int j, int k) {
return j + k;
}
// 注解方法声明
public @Recommended Integer f3(String str) {
return str.length() / 2;
}
// 注解异常类型
public void f4() throws @TypeAnno NullPointerException {
// ...
}
public static void test() {
TypeTest<@TypeAnno Integer> obj1 = new TypeTest<>(); // 注解实际类型参数
@TypeAnno TypeTest<Integer> obj2 = new @TypeAnno TypeTest<>(); // 注解实例类型
Object x = new Integer(10);
Integer y;
y = (@TypeAnno Integer) x; // 注解强制类型转换
}
}
// 注解被继承的类型
public class SubTypeTest extends @TypeAnno TypeTest<Integer> {
// ...
}
注意
- 类型注解必须包含ElementType.TYPE_USE作为目标。
- 类型注解需要放到应用该注解的类型前面。
- 不能对void返回类型添加注解。
- @Target可以用于消除声明注解和类型注解的模糊性问题,比如上面例子中@TypeAnno注解字段类型而@NotNull注解字段声明;@TypeAnno注解方法返回类型而@Recommended注解方法声明。
this对象
this是所有实例方法的隐式参数,它的类型必须是其类的类型。类型注解可以注解this的类型,但是需要使用JDK 8的一个新特性,从JDK 8开始可以显式地将this声明为方法的第一个参数。
除非是要注解this的类型,否则没必要声明this。并且显示声明this没有改变方法签名,因为默认也会隐式声明this。
重复注解
JDK 8新增了另一种注解特性,这种特性称为重复注解,它允许在相同元素上重复应用同一个注解。
@Repeatable注解
可重复的注解必须用@Repeatable进行注解。@Repeatable定义在java.lang.annotation中,它的value域指定了重复注解的容器类型。源码如下所示:
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Repeatable {
/**
* Indicates the <em>containing annotation type</em> for the
* repeatable annotation type.
* @return the containing annotation type
*/
Class<? extends Annotation> value();
}
容器注解
重复注解的容器类型被指定为注解。容器注解的value域是重复注解类型的数组。要创建重复注解,必须创建容器注解,然后将该容器注解的类型作为@Repeatable注解的参数。
@Retention(RetentionPolicy.RUNTIME)
@Repeatable(RepeatedAnnos.class)
public @interface RepeatableAnno {
String name() default "test";
int value() default 100;
}
@Retention(RetentionPolicy.RUNTIME)
public @interface RepeatedAnnos {
RepeatableAnno[] value();
}
获取重复注解的方式
- 使用getAnnotation()方法获取重复注解,要使用容器注解作为参数,而不是重复注解。
public class RepeatableTest {
@RepeatableAnno(name = "first annotation", value = 10)
@RepeatableAnno(name = "second annotation", value = 20)
public static void test() {
RepeatableTest obj = new RepeatableTest();
try {
Class<?> c = obj.getClass();
Method m = c.getMethod("test");
Annotation anno = m.getAnnotation(RepeatedAnnos.class);
System.out.println(anno);
} catch (NoSuchMethodException e) {
System.out.println("Method Not Found.");
}
}
public static void main(String[] args) throws Exception {
test();
}
}
上面例子的输出如下:
@RepeatedAnnos(value=[@RepeatableAnno(name=first annotation, value=10), @RepeatableAnno(name=second annotation, value=20)])
- 获取重复注解的另一种方式是使用JDK 8添加到AnnotatedElement中的新方法,它们能够直接操作重复注解。这些方法包括getAnnotationsByType()和getDeclaredAnnotationsByType()。
Annotation[] annos = m.getAnnotationsByType(RepeatableAnno.class);
for (Annotation anno : annos) {
System.out.println(anno);
}
一些限制
使用注解声明有一些限制:
- 一个注解不能继承另一个注解。
- 注解声明的成员方法不能带参数。
- 注解声明的成员方法不能指定throws子句。
- 注解不能被泛型化。
Java8对注解处理提供了两点改进:可重复的注解及可用于类型的注解
1.可重复注解
Java8以前定义注解:
@Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE})
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotation {
String value() default "mobei";
}
使用:
可以看到这时当做重复注解使用就报错了。
如果要使用重复注解,必须先定义一个容器:
//注解容器
@Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE})
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotations {
MyAnnotation[] value();
}
在重复注解上标注@Repeatable注解并指定容器类:
//使用可重复注解:①必须使用@Repeatable注解标注该注解②在@Repeatable注解中指定该注解的注解容器
@Repeatable(MyAnnotations.class)
@Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE, TYPE_PARAMETER})//TYPE_PARAMETER:J8中新增加的类型注解
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotation {
String value() default "mobei";
}
使用:
不报错,测试:
@Test
public void test1() throws Exception {
Class<TestAnnotation> clazz = TestAnnotation.class;
Method m1 = clazz.getMethod("show");
MyAnnotation[] mas = m1.getDeclaredAnnotationsByType(MyAnnotation.class);
for (MyAnnotation myAnnotation : mas) {
System.out.println(myAnnotation.value());
}
}
输出:
Hello
World
2.类型注解
在上面的MyAnnotation的@Target注解中加入TYPE_PARAMETER
//使用可重复注解:①必须使用@Repeatable注解标注该注解②在@Repeatable注解中指定该注解的注解容器
@Repeatable(MyAnnotations.class)
@Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE, TYPE_PARAMETER})//TYPE_PARAMETER:J8中新增加的类型注解
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotation {
String value() default "mobei";
}
可以修饰参数:
@MyAnnotation("Hello")
@MyAnnotation("World")
public void show(@MyAnnotation("abc") String str){
}