注解(Annotation)是对程序的一种描述和说明,可以理解为是程序的元数据(metadata),它对被注解的代码没有直接的影响,但是我们可以通过反射机制获取注解,然后让处理这些注解称为程序的一部分。本文介绍注解的基本内容。
我们在重写(Override)一个方法时,经常会这样写:
<span style="background-color: rgb(255, 255, 51);">@Override</span> public void close() throws Exception { // TODO Auto-generated method stub }上面黄色背景的@Override就是一种注解,它对close()这样方法作出说明:这个方法是重写超类或者接口的方法。即注解是用来说明程序本身的数据。把注解(Annotation)和注释(comment)区分开来很重要,注释一般是写给开发人员(使用你的代码的人)看的,帮助别人理解你的代码,对程序毫无影响,编译器对注释一无所知,编译的时候完全忽略注释。但是注解可以被编译器甚至程序所使用的。其实在@Override背后的,是一个注解类型(annotation type),就像类是一种类型一样。这个注解类型再java.lang.Override.java中定义,查看源码如下:
@Target(ElementType.METHOD) @Retention(RetentionPolicy.SOURCE) public @interface Override { }这就是Override这个注解类型的定义,注意注解是一种类型(type),与类、接口、枚举是一个概念层次的。实际上你在Eclipse使用右键“新建”菜单的时候,你会看到下面的界面:
我们看到了Annotation、Class、Enum、Interface。我想表达的意思就是注解它也是一种类型,后面我们会看到可以把它看做是一种特殊的接口,就像Enum看作是一种特殊的类一样。
定义注解类型或者直接使用预定义的注解类型有什么用途,其中包括:
编译器可以根据注解来检测代码错误或者给出警告信息。以Override注解为例,如果你想重写父类方法或者实现接口方法,在开始定义方法之前先使用@Override注解,那么如果你定义方法时给出的方法名或者参数类型与父类(接口)中定义的不一样,则编译器无法通过。比如,
/** * @author Brandon B. Lin * */ public class MyThread extends Thread { @Override public void run(int a){ } @Override public void rnn(){ } }我们继承了Thread类,并且在重写方法之前使用注解@Override,但是在定义方法的时候,由于拼写错误或者其他原因导致方法签名与父类不匹配,那么会出现编译错误。如果你没有使用注解,那么编译器理解为你在子类重新添加了一个方法,于是编译正常通过,即使方法名只相差一个字符。
/** * @author Brandon B. Lin * */ public @interface ClassAnnotation { String version(); String lastModified() default "N/A"; String reviewer(); }访问控制符public,然后关键字@interface,注解名称,{ }.在注解里面,定义了注解的成员(或者说是属性),虽然它看起来更像是方法的声明,注意没有方法体,方法体有Java自动实现。注意到属性lastModied后面有个default “N/A”,顾名思义,就是为属性提供一个默认值。定义好之后,我们就可以使用了:
/** * @author Brandon B. Lin * */ @ClassAnnotation(version = "1.0", lastModified = "2014/5/1", reviewer = "John, Gil, Brandon") public class UseAnnotation { /** * @param args */ public static void main(String[] args) { // TODO Auto-generated method stub } }在定义类之前,使用注解,然后将注解属性的值放在()中,用逗号隔开。注意使用注解时,如果不在同一个包,也必须使用import语句导入,并且能否导入收到注解访问控制符的限制,限制的规则同类访问控制符是一样的。
Class<<span style="background-color: rgb(255, 255, 0);">? extends Annotation</span>> annotationType()该方法返回表示该注解的Class对象,java.lang.Class这个类时用来抽象java当中的类型的,包括类、接口、枚举和注解。用来抽象注解的时候,表示的是注解的类型。注意Class本身就是一个类,假如我们定义了三个接口Annotation1、Annotation2、Annotation3,那么这三个注解类型的对象调用annotationType方法的时候,分别返回Class类的三个对象,我们几位obj1、obj2、obj3。因此关系就很明了了,Class类用来抽象注解类型,而可能存在很多个注解类型,预定义的,自己定义的,每一个注解类型就用一个Class对象来描述,比如obj1、obj2、obj3分别描述注解Annotation1,Annotation2/Annotation3. 再看看返回类型,这里涉及到泛型了,尖口号的内容是Class类的类型参数(type parameter),并且使用通配符?和extends对类型参数进行限制,即返回的Class类型的类型参数必须是Annotation或者其子类型,而所有注解都自动拓展Annotation接口,所以都是符合要求的,比如上面的ClassAnnotation对象调用annotationType返回的对象就属于Class<ClassAnnotation>类型。
public static void main(String[] args) { Throwable t1 = new Throwable(); Throwable t2 = new Throwable(); System.out.println(t1.getClass() == t2.getClass()); System.out.println(t1.getClass() == Throwable.class); }最终输出都是true。Throwable类型的对象t1和t2获得的Class<Throwable>对象都是同一个对象,所以也可以直接通过Throwable.class获得。注意.class不是方法。
String toString()返回表示该注解对象的字符串,返回的格式取决于具体的实现,但是一般格式为:
@com.acme.util.Name(first=Alfred, middle=E., last=Neuman)hashCode和equals方法同其他类型一样。API文档中说toString、hashCode、equals方法是重写了类Object中对应的方法,也就说,java中的类型,无论是类、接口还是枚举、注解都最终扩展自Object类,其方法在各种类型中均可使用。
public @interface MyAnno { String str() default "test"; int val() default 900; }在使用这个注解的时候,可以有以下4中方式:
@MyAnno() @MyAnno(str = "else") @MyAnno( val = 100) @MyAnno(str = "else", val = 100)在使用注解时,对于成员我们一般按照注解定义中的顺序给出值,但是也可以不按这个顺序来。
public @interface SingleAnno { String value(); }使用这个单成员的时候可以这么来:
@SingleAnno("TT")可以不指定成员名称,可以的意思就是你要指定也没有问题。如果成员不止一个,但是其他成员都有默认值,也可以不指定value这个成员的名称,直接使用上面的形式。例如成员为 String value(); int a() default 0; 一样可以使用@SingleAnno(“TT”)这样的形式。
<span style="background-color: rgb(255, 255, 0);">@Retention</span>(RetentionPolicy.SOURCE) public @interface MyAnno { String str() default "test"; int val() default 900; }我们定义了一个注解MyAnno,同时使用另一个注解Retention对MyAnno进行注解,Retention就称为注解的注解。另外,我们可以指定某个注解可以被用到什么地方,比如我们可以指定MyAnno只能用于注解方法,这是通过注解Target指定的。Target是一个内置的注解类型,部分代码如下:
public @interface Target { /** * Returns an array of the kinds of elements an annotation type * can be applied to. * @return an array of the kinds of elements an annotation type * can be applied to */ ElementType[] value(); }它是个单成员注解,也是注解的注解,其成员value指定被注解的注解可以在哪些地方使用。value是一个数组,每个数组元素的取值为枚举类型java.lang.annotation.ElementType的枚举常量。比如,对于Target注解,它使用自身指定可以使用它的程序元素:
@Target(ElementType.ANNOTATION_TYPE) public @interface Target { ...可以看到,Target这个注解只能用于注解注解 (本身或者其他),其他地方都不能使用。再比如Override注解只能用于注解方法,所以在Override的定义像这样子:
@Target(ElementType.METHOD) @Retention(RetentionPolicy.SOURCE) public @interface Override { }
@Target(value={CONSTRUCTOR, FIELD, LOCAL_VARIABLE, METHOD, PACKAGE, PARAMETER, TYPE}) public @interface Deprecated { }可以看到成员是空的。(PS:如果你发现value后面的值很奇怪,看一下源码会发现是因为使用了静态导入),使用了Deprecated标记注解就说明该元素(比如说方法)已经过时了,相反的,没有使用该标记就是没有过时。因此,使用标记注解的时候,无需给出成员的值,()都可以省略。标注注解用于二元情况,即非黑即白,要么过时要么不过时。比如另一个标记注解Documented,这个注解也是注解的注解,在某个注解中使用了Documented注解,就表明这个注解将被文档化,反之不使用则表示不会被文档化。
<span style="background-color: rgb(255, 255, 0);">@Retention(RetentionPolicy.SOURCE)</span> public @interface MyAnno { String str() default "test"; int val() default 900; }Retention在java.lang.reflect包中定义,只能用于注解自身或者其他注解,它用来指定某一注解的生存时间,部分源码如下:
public @interface Retention { /** * Returns the retention policy. * @return the retention policy */ RetentionPolicy value(); }这是一个单成员注解,并且使用了value名称,所以使用的时候可以不指出成员名称。唯一成员的类型是枚举类型RetentionPolicy。这个枚举类型中定义了三个常量SOURCE、CLASS、RUNTIME,含义如下:
public class UseAnnotation { /** * @param args */ public static void main(String[] args) { getAnno(); } @MyAnno(str = "TT", val = 10) public static void getAnno() { // UseAnnotation use = new UseAnnotation(); Class<?> useClass = UseAnnotation.class; try { Method m = useClass.getMethod("getAnno"); System.out.println(m.getName()); <span style="background-color: rgb(255, 102, 102);">MyAnno anno = m.getAnnotation(MyAnno.class);</span> System.out.println(anno.str() + " " + anno.val()); } catch (NoSuchMethodException ex) { System.out.println("Method not found"); } } }我们在静态方法getAnno定义的时候使用了MyAnno注解,指定两个成员的值为“TT”和10.然后在方法中获取注解,输出其成员的值。MyAnno代码如下:
<span style="background-color: rgb(255, 255, 0);">@Retention(RetentionPolicy.RUNTIME)</span> public @interface MyAnno { String str() default "test"; int val() default 900; }注意黄色部分将保留策略指定为RUNTIME,如果将这个去掉变成默认的保留策略,则程序将抛出NullPointerException,因为运行时候无法获取注解,红色背景的一行将抛出异常!
@Documented @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.ANNOTATION_TYPE) public @interface Inherited { }这个注解只能用于注解注解,如果某个注解是可继承的(即注解定义中使用了@Interited),那么注解将会被继承。值得注意的是,被Inherited注解的注解,只影响类声明的注解,对其他诸如方法、构造器都没有影响(即使使用了)。如果MyAnno注解使用了Inherited注解,然后我们在类Father的声明中使用了注解MyAnno,其子类Son的声明将继承这个注解,可以在运行中通过反射获取。如果MyAnno没有使用Inherited注解,则不会继承。另外一点,注解的继承只存在于子类与父类,在接口与它的实现类之间不存在注解继承。
<span style="background-color: rgb(255, 255, 0);">@Repeatable(Schedules.class)</span> public @interface Schedule { String str();}其中,括号中的Schedules.class用于容纳重复的注解,Schedules本身也是个注解,编译器编译的时候将重复的注解存在Schedules容器中。如果没有使用Repeatable注解,重复使用注解将导致编译错误。
public @interface Schedules { Schedule[] value(); }作为容器的注解必须有一个value成员,它的类型是重复注解的数组。然后可以这么使用:
@Schedule(str = "TT")
@Schedule(str = "T")重复的注解可以通过两种方式提取,示例代码如下:
Class<?> c = Reapt.class; MyAnnos annos = c.getAnnotation(MyAnnos.class); for (MyAnno a : annos.value()) { a.value(); } MyAnno[] aa = c.getAnnotationsByType(MyAnno.class); for (MyAnno a : aa) { a.val(); }其中getAnnotationsByType将以数组的形式返回所有指定类型的注解。