注解
创建注解
下面代码创建了一个名为MyAnno的注解,有两个成员str和val。@Retention(…)注解是为了在运行时通过反射获取该注解,现在可以忽略,后文会详细阐述。
@Retention(RetentionPolicy.RUNTIME)
@interface MyAnno {
String str();
int val();
}
首先,注意关键字interface前面的@,这告诉编译器正在声明一种注解类型。接下来注意两个成员都只包含方法声明。但是,不能为这些方法提供方法体,这是由Java实现这些方法。此外,正如后面即将看到的,这些方法的行为更像是域变量。
注解不能包含extends子句。但是,所有注解类型都自动扩展了Annotation接口。因此Annotation是所有注解的超接口。但是该接口实在java.lang.annotation中声明的,其中重写了hashCode()、equals()以及toString()方法,这些方法是由Object方法定义的,另外还制定了annotationType()方法,该方法返回表示调用注解的Class对象。
指定保留策略
保留策略决定了在什么位子丢弃注解。Java定义了三种策略,他们被封装到java.lang.annotation.RetentionPolicy枚举中。这三种策略分别是SOURCE/CLASS/RUNTIME。
- @Retention(RetentionPolicy.SOURCE):仅存在于源码中,在class字节码文件中不包含。
- @Retention(RetentionPolicy.CLASS):默认的保留策略,注解编译后存在于class字节码文件中,但运行时不能获取。
- @Retention(RetentionPolicy.RUNTIME):注解存在于class字节码文件中,运行时可以获取。
注意:局部变量声明的注解不能存储在.class文件中。
在运行时通过反射获取注解
下面声明了一个方法myMeth(),并且用@MyAnno(str="my annotation", val=100)修饰。main方法用反射获取myMeth()方法上@MyAnno注解,并且在控制台输出其两个成员。代码中getAnnotation()方法用来获取指定类型的注解。
测试代码
public class MyAnnoTest {
@MyAnno(str="my annotation", val=100)
public void myMeth(){
}
public static void main(String[] args) {
Class<?> clazz = MyAnnoTest.class;
Method method;
try {
method = clazz.getMethod("myMeth");
MyAnno myAnno= method.getAnnotation(MyAnno.class);
System.out.println(myAnno.str()+" "+myAnno.val());
} catch (NoSuchMethodException | NullPointerException e) {
e.printStackTrace();
}
}
}
测试结果
my annotation 100
Process finished with exit code 0
AnnotatedElement接口
AnnotatedElement接口在java.lang.reflect中定义,这个接口支持注解反射,并且Method/Field/Constructor/Class/Package都实现了该接口。
AnnotatedElement接口中定义了一系列操作注解的方法,如下图所示:
-
getAnnotation
如果存在这样的注解,则返回该元素的指定类型的注解,否则返回null。
-
getAnnotations
返回此元素上存在的注解。如果此元素上没有注解,则返回值为长度为0的数组。此方法的调用者可以自由修改返回的数组;它将对返回给其他调用者的数组没有影响。
-
getAnnotationsByType
返回与此元素相关联的注解。如果没有与此元素相关联的注解,则返回值为长度为0.的数组。此方法与getAnnotation(Class)之间的区别在于此方法检测其参数是否为可重复注解类型(JLS 9.6),如果因此,尝试通过“查看”容器注释来查找该类型的一个或多个注解。该方法的调用者可以自由修改返回的数组;它将对返回给其他调用者的数组没有影响。
-
getDeclaredAnnotation
如果这样的注解直接存在,则返回指定类型的元素注解,否则返回null。此方法忽略继承的注解。 (如果此元素上没有注解,则返回null)
-
getDeclaredAnnotations
返回直接存在于此元素上的注解。此方法忽略继承的注解。如果此元素上没有直接显示的注解,则返回值为长度为0的数组。此方法的调用者可以自由修改返回的数组;它将对返回给其他调用者的数组没有影响。
-
getDeclaredAnnotationsByType
如果此类注解直接存在或间接存在,则返回该元素的注解(指定类型)。此方法忽略继承的注解。如果在该元素上没有直接或间接存在的指定注解,则返回值为长度为0.的数组。此方法与getDeclaredAnnotation(Class)之间的区别在于该方法检测其参数是否是可重复的注解类型(JLS 9.6 ),如果是,尝试通过“查看”容器注解(如果存在)来查找该类型的一个或多个注解。该方法的调用者可以自由修改返回的数组;它将对返回给其他调用者的数组没有影响。
-
boolean isAnnotationPresent(Class<? extends Annotation> annoType>)
判断指定的注解是否和调用对象相关联。
测试代码:
下面的代码片段创建了AnnotatedElementTest的父类AnnotatedElementTestSuper,其被@SheAnno修饰
@SheAnno(name="kylin")
public class AnnotatedElementTestSuper {
}
下面的代码片段创建了注解@MyAnno,并且有两个成员str()和val(),被@Retention(RetentionPolicy.RUNTIME)修饰,说明其可以在运行时获取。
@Retention(RetentionPolicy.RUNTIME)
@interface MyAnno {
String str();
int val();
}
下面的代码创建了注解@Retention(RetentionPolicy.RUNTIME),拥有成员name(),可以在运行时获取。@Inherited注解说明其可以被子类继承,即@MyAnno注解修饰的父类A的子类B会继承@MyAnno注解。更多关于@Inherited参见后文。
@Inherited
@Retention(RetentionPolicy.RUNTIME)
public @interface SheAnno {
String name();
}
测试类,下面的代码主要测试了以下四个方法:
- getDeclaredAnnotations()
- getAnnotations
- isAnnotationPresent
- getAnnotationsByType
第一个和第二个方法,为了比较有无Declared的区别,第三个判断一个注解是否和调用对象相关,第三个参见后文的重复注解。
上面7个方法中,还有未测试的方法,建议读者自行测试,对比其不同的地方,有助于加深理解AnnotatedAnnotation接口。
测试AnnotatedAnnotation接口:
@MyAnno(str = "string", val = 100)
public class AnnotatedElementTest extends AnnotatedElementTestSuper{
public static void main(String[] args) {
Class<?> clazz = AnnotatedElementTest.class;
/*
* 获取声明的注解
*/
System.out.println("get declared annotations");
Annotation[] declaredAnnotations = clazz.getDeclaredAnnotations();
for (Annotation a :
declaredAnnotations) {
System.out.println(a);
}
/*
* 获取相关的所有注解,包括继承下来的(与getDeclaredAnnotations()不同之处)。
* 能继承的类上面的注解,需用@Inherited修饰
*/
System.out.println("get all annotations");
Annotation[] allAnnotations = clazz.getAnnotations();
for (Annotation a :
allAnnotations) {
System.out.println(a);
}
/*
* 判断注解是否与类相关
*/
boolean isAnnotationPre = clazz.isAnnotationPresent(MyAnno.class);
System.out.println("Annotation MyAnno is present at Class AnnotatedElementTest? " + isAnnotationPre);
isAnnotationPre = clazz.isAnnotationPresent(SheAnno.class);
System.out.println("Annotation SheAnno is present at Class AnnotatedElementTest? " + isAnnotationPre);
/*
*根据类型获取注解
*/
System.out.println("get annotation by type");
Annotation[] annotations = clazz.getAnnotationsByType(MyAnno.class);
for (Annotation a :
annotations) {
System.out.println(a);
}
}
}
结果
get declared annotations
@annotation.customize.MyAnno(str=string, val=100)
get all annotations
@annotation.customize.SheAnno(name=kylin)
@annotation.customize.MyAnno(str=string, val=100)
Annotation MyAnno is present at Class AnnotatedElementTest? true
Annotation SheAnno is present at Class AnnotatedElementTest? true
get annotation by type
@annotation.customize.MyAnno(str=string, val=100)
Process finished with exit code 0
使用默认值
如果没有为注解的成员赋值时,则使用默认值。使用default子句声明注解成员的默认值。
声明使用默认值的注解:
@Retention(RetentionPolicy.RUNTIME)
public @interface DefautValAnno {
String name() default "name";
int value() default 100;
}
测试:
@DefautValAnno
public class DefautValAnnoTest {
public static void main(String[] args) {
Class<?> clazz = DefautValAnnoTest.class;
DefautValAnno defautValAnno = clazz.getAnnotation(DefautValAnno.class);
System.out.println(defautValAnno.name() + " " + defautValAnno.value());
}
}
结果:
name 100
Process finished with exit code 0
标记注解
标记注解是特殊类型的注解,其中不包含成员。标记注解唯一的就是标记声明。因此,这种注解作为注解而存在的理由是充分的。确定标记注解是否存在的最好方式是使用isAnnotationPresent()方法,该方法是有AnnotatedElement接口定义的。
@Retention(RetentionPolicy.RUNTIME)
public @interface MakerAnno {
}
单成员注解
单成员注解只包含一个成员。除了允许使用缩写形式指定成员的值之外,单成员注解的工作方式和常规注解类似。如果只有一个成员,应用注解时就可以简单地为该成员指定值,而不需要指定成员的名称。但是,为了使用这种缩写形式,成员名必须是value。
单成员注解的声明:
public @interface SingleParamAnno {
String value();
String name() default "aaa";
}
单成员注解的使用:
/**
* 注解只有一个成员且成员名为value时,使用注解时可直接在括号中填入值。
* 若其他成员都有默认值,则也可以使用单成员注解。
*/
@SingleParamAnno("siyao")
public class SingleParamAnnoTest {
@SingleParamAnno(value = "siyao", name = "bbbb")
public void testMethod() {
}
}
Java内置注解
Java提供许多内置注解,大部分时专用注解,但是有9个用于一般目的。
元注解
java.lang.annotation包中:
-
@Retention:被设计为只能用于注解其他注解,用来指定保留策略。
保留策略有三种:
- @Retention(RetentionPolicy.SOURCE):仅存在于源码中,在class字节码文件中不包含。
- @Retention(RetentionPolicy.CLASS):默认的保留策略,注解编译后存在于class字节码文件中,但运行时不能获取。
- @Retention(RetentionPolicy.RUNTIME):注解存在于class字节码文件中,运行时可以获取。
@Retention(RetentionPolicy.RUNTIME) @interface MyAnno { String str(); int val(); }
-
@Documented:注解时标记接口,用于通知某个工具----注解将被文档化,只能注解其他注解。用于指定可以应用注解的类型,被设计为只能注解其他注解。@Target只有一个参数,这个参数必须是来自ElementType常量,这个参数指定了将为其应用注解的声明类型。
@Documented @Retention(RetentionPolicy.RUNTIME) public @interface DefautValAnno { String name() default "name"; int value() default 100; }
-
@Target
用于指定可以应用注解的类型,被设计为只能注解其他注解。@Target只有一个参数,这个参数必须是来自ElementType常量,这个参数指定了将为其应用注解的声明类型。
目标常量 可应用注解的声明类型 ANNOTATION_TYPE 另外一个注解 CONSTRUCTOR 构造函数 FIELD 域变量 LOCAL_VARIABLE 局部变量 METHOD 方法 PACKAGE 包 PARAMETER 参数 TYPE 类、接口或枚举 TYPE_PARAMETER 类型参数 TYPE_USE 类型使用 @Target({ElementType.TYPE, ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) public @interface MakerAnno { }
-
@Inherited
是标记注解,只能用于另外一个注解声明。@Inherited会导致超类的注解被子类继承。
继承各种情况的详细说明:
@Inherited @Retention(RetentionPolicy.RUNTIME) public @interface SheAnno { String name(); }
其他内置注解
java.lang包中:
-
@Override
标记注解,只能用于方法。使用带有@Override注解的方法必须重写超类中的方法。如果不这样,则会出现编译错误。@Override可以确保方法被正真重写。
示例:
@Override public void run() { try { for (int i = 0; i < 5; i++) { System.out.println("thread: "+name+"time: "+i); Thread.sleep(500); } } catch (InterruptedException e) { System.out.println("thread: "+name+" is interrupted"); } System.out.println("thread: "+name+" is exiting"); }
-
@Deprecated
标记注解,用于指示声明是过时的,并且已经被新的形势取代。
示例:
@Deprecated public void deprecatedMethod(){ }
-
@FunctionalInterface
标记注解,用于接口。指出被注解的接口是一个函数式接口。函数式接口是指仅包含一个抽象方法的接口,由lambda表达式使用。如果被注解的接口不是函数式接口,将报告编译错误。创建函数式接口并不需要使用此注解,使用注解仅仅是在于提供信息。
示例:
@FunctionalInterface Interface FI{ abstract judge(int a); abstract equals(); }
-
@SafeVarargs
标记注解,只能用于方法和构造函数,指示没有发生与可变长度参数相关的不安全动作。如果不安全代码不能具体化的varargs类型相关,或者与参数化的数组实例相关,那么@SafeVarargs注解用于抑制“未检查不安全代码”警告。@SafeVarargs只能用于varargs方法或者声明为static 或final的构造函数。
示例:
@SafeVarargs public static <T> T useVarargs(T... args) { return args.length > 0 ? args[0] : null; }
详情戳下面的链接:
-
@SuppressWarning
注解用于指定能抑制一个或多个编译器可能会报告的警告。使用以字符串形式表示的名称来指定要被抑制的警告。
类型注解
从JDK 8开始,Java增加了可以使用注解的地方。最早的注解只能应用于声明。但是,在JDK 8中,在能够使用类型的大多数地方,也可以指定注解。扩展后的这种注解称为类型注解。例如,可以注解方法的返回类型、方法内this类型、强制转换、数组级别、被继承的类以及throws子句。可以注解泛型,包括泛型参数边界和泛型类型参数。
类型注解很重要,因为他们允许工具对代码执行额外的检查,从而帮助避免错误。需要理解,javac本身一般不执行这些检查,所以需要使用单独的工具,不过这种工具可能需要作为编译器插件发挥作用。
类型注解必须包含ElementType.TYPE_USE作为目标,用@Target指定。类型注解需要放到应用该注解的类型的前面。
下面的代码,详细列出了类型注解的使用方式:
public class TypeAnnoTest <@TypeAnno("type parameter") T> {
//use a type annotation on a field
@TypeAnno("field") String string;
//use a type annotation on a constructor
public @TypeAnno("unique") TypeAnnoTest(){
}
//use a type annotation to annotate this
public void method (@TypeAnno("this") TypeAnnoTest<T> this, int a, String str){
}
//annotate return type
public @TypeAnno("return") int method2(){
return 1;
}
//use a type annotation with a throws clause
public void meghod3() throws @TypeAnno("throws") NullPointerException{
}
//annotate the array element type
@TypeAnno("array") String [] strings;
public void method4(){
//use a type annotation on a type argument
TypeAnnoTest<@TypeAnno("argument") Integer> ob = new TypeAnnoTest<>();
//use a type annotation with new
@TypeAnno("new") TypeAnnoTest<String> ob2 = new TypeAnnoTest<>();
//use a type annotation on a cast
Object x = new Integer(10);
Integer y = (@TypeAnno("cast") Integer) x;
}
}
//use type annotation with inheritance clause
class TypeAnnoTestSub extends @TypeAnno("inherit") TypeAnnoTest<String>{
}
重复注解
JDK 8中新增的另外一个特性允许在元素上重复应用注解,这种特性成为重复注解。可重复注解必须用@Repeatable进行注解。@Repeatable注解定义在java.lang.annotation中,其value域指定了重复注解的容器类型。容器被指定为注解,对于这种注解,value域是重复注解类型的数组。因此,要创建重复注解,必须创建容器注解。
声明重复注解
@Repeatable(RepeatableAnnoContainer.class)
@Retention(RetentionPolicy.RUNTIME)
@interface RepeatableAnno {
String name();
int value();
}
声明重复注解的容器注解
@Retention(RetentionPolicy.RUNTIME)
public @interface RepeatableAnnoContainer {
RepeatableAnno[] value();
}
测试代码
public class Test {
@RepeatableAnno(name = "kylin", value = 100)
@RepeatableAnno(name = "siyao", value = 200)
public void testMethod() {
}
public static void main(String[] args) {
Class<?> clazz = Test.class;
try {
Method method = clazz.getMethod("testMethod");
//使用getAnnotations()获取重复注解
Annotation[] getAnnotations = method.getAnnotations();
for (Annotation a :
getAnnotations) {
System.out.println(a);
}
//使用getAnnotationsByType()获取重复注解
Annotation[] annotations = method.getAnnotationsByType(RepeatableAnnoContainer.class);
for (Annotation a :
annotations) {
System.out.println(a);
}
} catch (NoSuchMethodException e) {
System.out.println("no such method");
}
}
}
结果
@annotation.repeatable.RepeatableAnnoContainer(value=[@annotation.repeatable.RepeatableAnno(name=kylin, value=100), @annotation.repeatable.RepeatableAnno(name=siyao, value=200)])
@annotation.repeatable.RepeatableAnnoContainer(value=[@annotation.repeatable.RepeatableAnno(name=kylin, value=100), @annotation.repeatable.RepeatableAnno(name=siyao, value=200)])
Process finished with exit code 0
一些限制
使用注解声明有许多限制。首先,一个注解不能继承另一个注解。其次,注解声明的所有方法都必须不带参数。此外,他们不能返回一下类型的值:
- 基本类型,例如int或double
- String或Class类型的对象
- 枚举类型
- 其他注解类型
- 上述类型的数组
注解不能泛型化。换句话说,他们不能带有类型番薯。最后注解方法不能指定throw子句。