http://blog.youkuaiyun.com/bingduanlbd/article/details/28093605
注解(Annotation)是对程序的一种描述和说明,可以理解为是程序的元数据(metadata),它对被注解的代码没有直接的影响,但是我们可以通过反射机制获取注解,然后让处理这些注解称为程序的一部分。本文介绍注解的基本内容。
1. 从一个例子开始:
我们在重写(Override)一个方法时,经常会这样写:
- <span style="background-color: rgb(255, 255, 51);">@Override</span>
- ublic void close() throws Exception {
- // TODO Auto-generated method stub
- @Target(ElementType.METHOD)
- @Retention(RetentionPolicy.SOURCE)
- public @interface Override {
- }
我们看到了Annotation、Class、Enum、Interface。我想表达的意思就是注解它也是一种类型,后面我们会看到可以把它看做是一种特殊的接口,就像Enum看作是一种特殊的类一样。
2. 注解的用途
定义注解类型或者直接使用预定义的注解类型有什么用途,其中包括:
2.1 information for compiler
编译器可以根据注解来检测代码错误或者给出警告信息。以Override注解为例,如果你想重写父类方法或者实现接口方法,在开始定义方法之前先使用@Override注解,那么如果你定义方法时给出的方法名或者参数类型与父类(接口)中定义的不一样,则编译器无法通过。比如,
- /**
- * @author Brandon B. Lin
- *
- */
- public class MyThread extends Thread {
- @Override
- public void run(int a){
- }
- @Override
- public void rnn(){
- }
- }
2.2 compile-time and deploy-time processing
开发和部署工具可以根据注解自动生成代码、XML文件等其他文件。
2.3 runtime processing
如果注解在运行时依然存在,那么可以通过反射机制获取注解内容,并对注解进一步处理,从而“融入”到程序中。
3. 定义和使用注解
3.1 定义注解
跟定义一个类一样,定义一个注解也需要遵循Java语言规范的格式。为了定义一个注解,使用@interface代替类定义中的class关键字。例如,为了描述一个类的元数据,我们定义一个简单的注解:
- /**
- * @author Brandon B. Lin
- *
- */
- public @interface ClassAnnotation {
- String version();
- String lastModified() default "N/A";
- String reviewer();
- }
- /**
- * @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
- }
- }
3.2 几点说明
1)注解不能使用extends关键字继承,所有的注解都自动拓展java.lang.annotation.Annotation接口,Annotation是所有注解的超接口。注解继承接口,好像有点难以理解,但是我们前面说到,可以把注解看做是一种特殊的接口。需要注意的是,如果一个接口手动地继承了Annotation接口,它并没有定义注解类型。Annotation接口本身也不是注解类型,不能使用。Annotation接口声明了几个方法:annotationType,hashCode,equals,toString。简单介绍一些这几个方法:
- Class<<span style="background-color: rgb(255, 255, 0);">? extends Annotation</span>> annotationType()
每个类型对应一个Class对象,同一个类型的所有对象返回的Class对象都相同,比如:
- 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);
- String toString()
- @com.acme.util.Name(first=Alfred, middle=E., last=Neuman)
2)默认值
在定义注解的成员是,如果使用default为成员(element)提供默认值,那么在使用注解的时候,可以不为这个成员提供对应的值(当然也可以提供)。默认成员的一般形式为:
type member( ) default value;
value的类型必须与type兼容。例如,对于注解:
- public @interface MyAnno {
- String str() default "test";
- int val() default 900;
- }
- @MyAnno()
- @MyAnno(str = "else")
- @MyAnno( val = 100)
- @MyAnno(str = "else", val = 100)
4 几种特殊的注解
注解中,有些注解跟常规注解不太一样,下面介绍几种特殊的注解类型
4.1 单成员注解
如果一个注解类型中的成员只有一个,称为单成员注解。在这种情况下,我们可以按照常规的注解方式定义成员、指定默认值以外,使用注解的时候可以不指定成员的名称,前提是把唯一的成员名称设为value。
- public @interface SingleAnno {
- String value();
- }
- @SingleAnno("TT")
总结起来就是,如果只有一个成员并且其名称为value,则使用注解时可以不指定成员名称而直接给出值。如果除了value还有其他成员,在其他成员都有默认值的情况下,也可以不指定value的名称。
4.2 注解的注解(元注解meta-annotation)
顾名思义就是用来注解注解的注解,哈哈。比如下面这个注解中:
- <span style="background-color: rgb(255, 255, 0);">@Retention</span>(RetentionPolicy.SOURCE)
- public @interface MyAnno {
- String str() default "test";
- int val() default 900;
- }
- 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();
- }
- @Target(ElementType.ANNOTATION_TYPE)
- public @interface Target { ...
- @Target(ElementType.METHOD)
- @Retention(RetentionPolicy.SOURCE)
- public @interface Override {
- }
4.3 标记注解
名称也很直接,用于标记。标记(maker)注解没有成员,比如我们经常看到的一个标注注解是Deprecated,它用于指示被注解的内容已经过时,被新的形势取代,看一下部分源码:
- @Target(value={CONSTRUCTOR, FIELD, LOCAL_VARIABLE, METHOD, PACKAGE, PARAMETER, TYPE})
- public @interface Deprecated {
- }
5. 在运行时使用反射获取注解
目前为止,注解除了上面所说的可以为编译器检测错误提供信息以外,似乎没有什么用处。虽然设计注解的主要目的是用于其它开发工具和部署工具,但是我们可以在运行时通过反射机制获得注解。有关反射的API在java.lang.reflect中定义。
使用反射的第一步是获取一个Class对象,关于Class类已经在3.2中介绍,可以使用object.getClass( )或者TypeName.class获取对应的Class对象。获取Class对象之后获取方法,获取方法之后获取注解。在给出实例之前,必须先介绍一下注解的保留策略。
5.1 注解保留策略
我们知道注释在编译的时候会被编译器丢掉,而对于注解来说,可以在编译的时候丢掉,也可以让编译器将它保留到字节码文件中。这是通过注解的保留策略(retention policy)来设置的。在定义一个注解的时候,我们可以指定其保留策略。例如:
- <span style="background-color: rgb(255, 255, 0);">@Retention(RetentionPolicy.SOURCE)</span>
- public @interface MyAnno {
- String str() default "test";
- int val() default 900;
- }
- public @interface Retention {
- /**
- * Returns the retention policy.
- * @return the retention policy
- */
- RetentionPolicy value();
- }
SOURCE:如果Retention成员取值为SOURCE,表明该注解只存在源代码当中,在编译的时候会被编译器丢掉。
CLASS:表示注解会被存到字节码文件.class中,但是虚拟机载入class文件的时候不会将注解导入,因此运行时无法得到。如果不在注解中使用Retention指定,则默认的保留策略为CLASS。
RUNTIME:不仅保存到class文件,还会被虚拟机载入内存,因此在运行的时候可以使用这些注解。
5.2 使用反射获取注解:
如果一个注解的保留策略指定为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");
- }
- }
- }
- <span style="background-color: rgb(255, 255, 0);">@Retention(RetentionPolicy.RUNTIME)</span>
- public @interface MyAnno {
- String str() default "test";
- int val() default 900;
- }
我们使用Method类型的对象m获取注解,这个getAnnotation由接口AnnotatedElement定义,该接口用于表示运行时被注解的元素,用于支持注解反射。类Method、Field、Constructor、Class、Package都实现了则个接口。
6. 几个重要问题
6.1 注解与继承
关于注解与继承,Java中定义了一个Inherited注解,源码如下:
- @Documented
- @Retention(RetentionPolicy.RUNTIME)
- @Target(ElementType.ANNOTATION_TYPE)
- public @interface Inherited {
- }
6.2 重复注解
Java 8引入了重复注解,即可以在同一个方法(或其他可以标注的元素)重复使用某个注解。要使用重复注解,必须经过以下2个步骤:
1)将要重复使用的注解用Repetable注解。格式如下:
- <span style="background-color: rgb(255, 255, 0);">@Repeatable(Schedules.class)</span>
- public @interface Schedule { String str();}
2)定义作为容器的注解:
- public @interface Schedules {
- Schedule[] 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();
- }
版权所有,转载请注明出处。