注解也称为元数据,它可以在代码中添加一些信息,我们可以通过编码的方式很方便地获取这些信息。注解可以分为三个部分:注解定义、注解使用、注解分析处理。
注解定义
注解的定义语法如下:
package com.shaoshuidashi;
public @interface TestAnnotation {
int value() default 0;
String desc() default "no desc";
}
上例中我们定义了一个名为TestAnnotation的注解,注解是用关键字@interface来定义的,大括号内的方法称为注解的元素,注解和接口很相似,区别是注解元素可以为其指定初始值,default关键字用来指定初始值。
注解中一般都会包含一些元素以表示某些值,当分析处理注解时,程序或工具可以利用这些值。没有元素的注解称之为标记注解,如:
package com.shaoshuidashi;
public @interface MarkAnnotation {
}
并不是所有的类型都可以作为注解元素,注解元素类型只能下面列表中的一种:
- 基本类型(byte、char、short、int、long、float、double、boolean)
- String
- Class
- enum
- Annotation
- 以上类型的数组
示例如下:
package com.shaoshuidashi;
public @interface ElementAnnotation {
int value() default 0;
String desc() default "no desc";
MarkAnnotation mark() default @MarkAnnotation(10);
Class someClass() default Integer.class;
Class[] classArray() default {String.class};
}
注解使用
注解使用示例如下:
@TestAnnotation(value=20, desc="Hello Annotation")
public class Test {
public static void main(String[] args) {
}
}
注解使用@+注解名的方式使用,如上例中的“@TestAnnotation(value=20, desc="Hello Annotation")”。注解的括号里面用来指定注解元素的值,上例中指定了元素value的值为20,元素desc的值为“Hello Annotation”。由于我们设置了元素的默认值,使用时可以不用设置元素的值可以去掉括号部分,如:“@TestAnnotation”。
有时我们会看到形如@TestAnnotation(20)的注解,括号里面没有指定元素名称,这是一种简化的写法,它有两个前提条件:
- 注解必须有个元素命名为value,括号里面的内容就是对value元素赋值。
- 括号里面只能有一个值,如果有多个值必须显式指定元素名。
所以@TestAnnotation(20)是把元素value赋值为20。
内置注解
除了我们自定义的注解以外,Java内置了一些有用的注解:
- @Override,表示当前的方法定义将覆盖父类的方法,如果方法签名对不上被覆盖的方法,编译器会发出错误提示。
- @Deprecated,表示即将弃用的代码,如果用户使用了这些代码,编译器将发出警告信息。
- @Suppress Warnings,用来关闭编译器警告。
除了上面的的注解,Java还内置了一些元注解,即用来修饰注解定义的注解:
- @Target 表示注解可以用在哪些地方。
- @Retention 表示需要在哪些阶段保存注解信息。
@Target注解的元素是一个ElementType枚举类型,它的值有:
- TYPE:类、接口(包括注解)、枚举声明
- FIELD:域,即类的成员变量和枚举常量声明
- METHOD:方法声明
- PARAMETER: 参数声明
- CONSTRUCTOR:构造器声明
- LOCAL_VARIABLE:局部变量声明
- ANNOTATION_TYPE:注解申明
- PACKAGE:包声明
@Retention的元素是一个RetentionPolicy枚举类型,它的值有:
- SOURCE:注解只保存在源码文件中,它在编译时将被编译器丢弃。
- CLASS:注解保存在class文件中,但会被VM丢弃。
- RUNTIME:运行时也会保留注解,可以通过反射机制获取注解。
下面是使用元注解的示例:
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface TestAnnotation {
int value() default 0;
String desc() default "no desc";
}
添加了元注解后,TestAnnotation注解只能用在方法声明前面,如果用在其它地方将出现编译错误,另外注解会在运行时保留。
注解分析处理
如果注解只是定义和使用那它和代码注释没什么两样。注解的关键在于分析和处理:对于运行时的注解,先使用反射机制获取注解信息,然后根据注解信息执行一些操作。下面是运行时注解的分析和处理示例代码:
package com.shaoshuidashi;
import java.lang.reflect.Method;
public class Test {
@TestAnnotation(10)
public void someMethod() {
}
@TestAnnotation(20)
public void anotherMethod() {
}
public static void main(String[] args) throws ClassNotFoundException{
Class<?> TestCls = Class.forName("com.shaoshuidashi.Test");
for(Method m: TestCls.getMethods()){
TestAnnotation ta = m.getAnnotation(TestAnnotation.class);
if (ta != null) {
System.out.println("find Annotation method: " +
m.getName() + " TestAnnotation value: " + ta.value());
}
}
}
}
/*输出:
find Annotation method: someMethod TestAnnotation value: 10
find Annotation method: anotherMethod TestAnnotation value: 20
*/
上面的代码打印出所有被注解的方法和注解的值,通过Method对象的的getAnnotation方法可以获取注解对象,如果方法没有被注解则返回null。注解对象和普通对象一样可以调用注解定义时设置的方法来获取元素的值,如上例中的ta.value()。
同理,我们可以通过调用Class和Field对象的getAnnotation方法来获取类注解和成员变量注解。
接下来
上面的示例属于运行时注解分析处理,那么源码级别的注解要怎么分析处理呢?我们将在下面一篇文章中单独介绍源码注解的分析处理。