注解的定义
- 定义
注解:提供一种为程序元素设置元数据的方法。
基本原则:注解不能直接干扰程序代码的运行,无论增加或删除注解,代码都能够正常运行。注解(也被成为元数据)为我们在代码中添加信息提供了一种形式化的方法,使我们可以在稍后某个时刻非常方便地使用这些数据。 ———摘自《Thinking in Java》
简单来说注解的作用就是将我们的需要的数据储存起来,在以后的某一个时刻(可能是编译时,也可能是运行时)去调用它。- 元数据
元数据:就是关于数据的数据
元数据的作用:
为什么要学习注解?
- 能够读懂别人使用注解实现的开源库
- 让编程变得更加简洁
注解的功能
- 作为特定的标记,用于告诉编译器一些信息
- 编译时动态处理,如动态生成代码
- 运行时动态处理,作为额外信息的载体,如得到注解信息
注解的分类
- 标准注解:Override、Deprecated、SuppressWarnings
标准 Annotation 是指 Java 自带的几个 Annotation,上面三个分别表示重写函数,不鼓励使用(有更好方式、使用有风险或已不在维护),忽略某项 Warning; - 元注解:@Retention、@Target、@Inherited、@Documented
元 Annotation 是指用来定义 Annotation 的 Annotation,在自定义注解部分将会详细介绍; - 自定义注解
自定义 Annotation 表示自己根据需要定义的 Annotation,定义时需要用到上面的元 Annotation
这里是一种分类而已,也可以根据作用域分为源码时、编译时、运行时 Annotation,后面在自定义 Annotation 时会具体介绍。
自定义注解
因为自定义注解,是使用元注解来实现的,所以我们先详细的了解一下元注解,然后再通过一个例子来讲解如何实现和使用自定义注解。
元注解
- @Target
说明了Annotation所修饰的对象范围:Annotation可被用于 packages、types(类、接口、枚举、Annotation类型)、类型成员(方法、构造方法、成员变量、枚举值)、方法参数和本地变量(如循环变量、catch参数)。在Annotation类型的声明中使用了target可更加明晰其修饰的目标。
作用:用于描述注解的使用范围(即:被描述的注解可以用在什么地方)
取值(ElementType)有:
- CONSTRUCTOR:用于描述构造器
- FIELD:用于描述域
- LOCAL_VARIABLE:用于描述局部变量
- METHOD:用于描述方法
- PACKAGE:用于描述包
- PARAMETER:用于描述参数
- TYPE:用于描述类、接口(包括注解类型) 或enum声明
@Target的源码
|
|
|
|
- @Retention
定义了该Annotation被保留的时间长短:某些Annotation仅出现在源代码中,而被编译器丢弃;而另一些却被编译在class文件中;编译在class文件中的Annotation可能会被虚拟机忽略,而另一些在class被装载时将被读取(请注意并不影响class的执行,因为Annotation与class在使用上是被分离的)。使用这个meta-Annotation可以对 Annotation的“生命周期”限制。
作用:表示需要在什么级别保存该注释信息,用于描述注解的生命周期(即:被描述的注解在什么范围内有效)
取值(RetentionPoicy)有:
- SOURCE:在源文件中有效(即源文件保留)
- CLASS:在class文件中有效(即class保留)
- RUNTIME:在运行时有效(即运行时保留)
@Retention的源码
|
|
|
|
- @Documented
用于描述其它类型的annotation应该被作为被标注的程序成员的公共API,因此可以被例如javadoc此类的工具文档化。Documented是一个标记注解,没有成员。
@Documented的源码
|
|
- @Inherited
@Inherited 元注解是一个标记注解,@Inherited阐述了某个被标注的类型是被继承的。如果一个使用了@Inherited修饰的annotation类型被用于一个class,则这个annotation将被用于该class的子类。
注意:@Inherited annotation类型是被标注过的class的子类所继承。类并不从它所实现的接口继承annotation,方法并不从它所重载的方法继承annotation。
当@Inherited annotation类型标注的annotation的Retention是RetentionPolicy.RUNTIME,则反射API增强了这种继承性。如果我们使用java.lang.reflect去查询一个@Inherited annotation类型的annotation时,反射代码检查将展开工作:检查class和其父类,直到发现指定的annotation类型被发现,或者到达类继承结构的顶层。
@Inherited的源码
|
|
实现和使用自定义注解
使用@interface自定义注解时,自动继承了java.lang.annotation.Annotation接口,由编译程序自动完成其他细节。在定义注解时,不能继承其他的注解或接口。@interface用来声明一个注解,其中的每一个方法实际上是声明了一个配置参数。方法的名称就是参数的名称,返回值类型就是参数的类型(返回值类型只能是基本类型、Class、String、enum)。可以通过default来声明参数的默认值。
定义注解格式
public @interface 注解名 {定义体}
注解参数的可支持数据类型
- 所有基本数据类型(int,float,boolean,byte,double,char,long,short)
- String类型
- Class类型
- enum类型
- Annotation类型
- 以上所有类型的数组
Annotation类型里面的参数该怎么设定:
- 首先,只能用public或默认(default)这两个访问权修饰.例如,String value();这里把方法设为defaul默认类型;
- 其次,参数成员只能用基本类型byte,short,char,int,long,float,double,boolean八种基本数据类型和 String,Enum,Class,annotations等数据类型,以及这一些类型的数组.例如,String value();这里的参数成员就为String;
- 最后,如果只有一个参数成员,最好把参数名称设为”value”,后加小括号.例:下面的例子FruitName注解就只有一个参数成员。
下面通过源码来展示自定义注解:
首先,我们自定义一个注解:AuthorAnnotation 来标记作者的信息
|
|
其次,再定义一个注解:BookAnnotation 来标记故事书籍的内容信息
|
|
最后,我们定义一种类型的书:LoveStoryBook,类注解标记的是《泡沫之夏》,为了区分,方法注解标记的是《微微一笑很倾城》
|
|
注解解析
上面已经将要注解的类和两个注解类实现了,下面定义一个类:ParseAnnotation,来解析我们自定义的注解
|
|
最后的最后就是验证我们自定义的注解是否正确:
|
|
运行结果
|
|
注意
- 对局部变量的注解只能在源码级别上进行处理,class文件并不描述局部变量。因此,所有的局部变量注解在编译完一个类的时候就会被遗弃掉。同样的,对包的注解不能在源码级别之外存在。
- 一条没有@Target限制的注解可以应用于任何项上。
- @Inherited元注解只能应用于对类的注解
参考资料
《Java核心技术卷二》
《Thinking in Java》
http://www.cnblogs.com/peida/archive/2013/04/26/3038503.html
http://a.codekk.com/detail/Android/Trinea/公共技术点之%20Java%20注解%20Annotation
http://www.cnblogs.com/peida/archive/2013/04/24/3036689.html