在注解Annotation全解析(一):我不是注释一文中,我们对于注解有了一个基本的认识:注解其实就是给我们代码中的一些关键节点(包、类、方法、参数、属性等)打上一些标记,然后在编译期或运行期通过判断这些标记来执行一些指定操作。那么通过这个流程我们可以分析出注解的使用结构是:
要想完全学会注解,光靠用别人定义的注解是不够的,所以我们要学会自己定义注解。另外,请在学习的下面内容的时候,打开你的Eclipse跟着内容进行编码。
基本定义语法
注解类型的声明部分
注解(Annotation)在Java中是一种与类、接口、枚举等同等地位的标示类型,所以它的基本定义语法与上面这几个都是一致的,只是所使用的关键字有所不同。
它使用的定义关键字是:@interface。这无疑表明了注解和接口是有关系的。实际上,注解其本质就是一种特殊的接口。在底层实现上,所有定义的注解(Annotation)都会自动继承java.lang.annotion.Annotation接口。但请注意,注解的声明只能使用@interface,如果自以为是的写成:
那么不好意思,LovoAnnotation不会被认为是注解类型,只会被当成一个普通的接口。
注解类型的实现部分
根据我们书写类、接口等自定义类型的经验,在这些类型的实现部分无非是书写属性、构造或方法这些内容。但是,注解(Annotation)在实现部分允许书写的内容与之前我们的经验完全不同。
注解(Annotation)在实现部分只能定义一个东西:注解类型元素(annotation type element)。我们来看看它的语法:
这个时候有些反应快的同学肯定会想:咦?这不就是接口里面默认定义抽象方法的语法吗?不要着急,我再写一个给你看:
还是抽象方法吗?打脸了吧!
注解里面定义的是:注解类型元素!注解类型元素!注解类型元素!重要的事情说三遍!
- 该元素的访问修饰符只能是public,不写也默认为public;
- 该元素的类型只能是基本数据类型、String、Class、注解类型(体现了注解嵌套的效果)、枚举类型以及上面这几种的一维数组;
- 该元素的名字应该一般都是名词形式,如果一个注解中只有一个类型元素,请把名字起成value(后面使用会带来便利操作);
- ()不是定义方法参数列表的地方,也不能在括号中定义任何参数,就是一个特殊语法;
- default代表默认值,值必须和第2点定义的类型保持一致;
- 如果没有默认值,代表后面使用时必须给该类型元素赋值;
不得不说,注解元素类型的语法非常奇怪,即有属性的特征(可以赋值),又有方法的特征(打上了一对圆括号)。但是这么设计是有道理的,我们在后面的系列文章中可以看到:注解(Annotation)在定义好了以后,使用的时候操作元素类型像在操作属性,解析的时候操作元素类型像在操作方法。
常用的元注解
一个最最基本的注解定义就只包括了上面的两部分内容:1、注解的名字;2、注解包含的类型元素。但是,我们在使用JDK自带注解的时候发现,有些注解只能写在方法上面(比如@Override);有些却可以写在类的上面(比如@Deprecated)。当然除此以外还有很多细节性的定义,那么这些定义该如何做呢?接下来就该元注解出场了!
元注解:专门修饰注解的注解。它们都是为了更好的设计自定义注解的细节而专门设计的。我们为大家一个个来做介绍。
@Target
@Target注解,是专门用来限定某个自定义注解能够被应用到哪些Java元素上面的。它使用一个枚举类型ElementType定义了如下几个选择:
枚举值 | 注解能够被应用的地方 |
---|---|
ElementType.ANNOTATION_TYPE | 注解类型的声明 |
ElementType.CONSTRUCTOR | 构造方法的声明 |
ElementType.FIELD | 属性的声明 |
ElementType.LOCAL_VARIABLE | 局部变量的声明 |
ElementType.METHOD | 方法的声明 |
ElementType.PACKAGE | 包的声明 |
ElementType.PARAMETER | 方法参数的声明 |
ElementType.TYPE | 类、接口以及枚举的声明 |
|
|
@Retention
@Retention注解,被翻译成保持力或持久力。它是用来限定某个自定义注解的生命力。注解的生命周期有三个阶段:仅存在java源文件是一个阶段;被编译到class文件是一个阶段;运行期被加载到Class对象中是第三个阶段。@Retention同样使用一个枚举类型RetentionPolicy定义了这三个选择:
枚举值 | 对应的生命力 |
---|---|
RetentionPolicy.SOURCE | 仅在Java源文件中 |
RetentionPolicy.CLASS | 被编译到class文件中 |
RetentionPolicy.RUNTIME | 运行期放到Class对象中 |
我们再详解一下:
- 如果一个注解被限定在Java源文件中,那么这个注解即不会参与编 译也不会在运行期起任何作用,这个注解就和一个注释是一样的效果,只能被阅读Java文件的人看到;
- 如果一个注解被编译到Class文件中,那么编译器可以在编译时根据注解做一些处理动作,但是运行时JVM(Java虚拟机)会忽略它,我们在运行期也不能读取到;
- 如果一个注解被定义在RetentionPolicy.RUNTIME,那么这个注解可以在运行期的加载阶段被加载到Class对象中。那么在程序运行阶段,我们可以通过反射得到这个注解,并通过判断是否有这个注解或这个注解中属性的值,从而执行不同的程序代码段。我们实际开发中的自定义注解几乎都是使用的RetentionPolicy.RUNTIME;
- 在默认的情况下,自定义注解是使用的RetentionPolicy.CLASS。
|
|
@Documented
@Documented注解,是被用来指定自定义注解是否能随着被定义的java文件生成到JavaDoc文档当中。
@Inherited
@Inherited注解,是指定某个自定义注解如果写在了父类的声明部分,那么子类的声明部分也能自动拥有该注解。@Inherited注解只对那些@Target被定义为ElementType.TYPE的自定义注解起作用。
一个完整的自定义Annotation示例
在学习了上面的各种定义注解的语法后,我们就可以根据我们的实际需要定义我们自己的自定义注解了。下面,我们就来定义一个:
当我们定义好了这个注解,那么接下来的事情就是如何使用它了。请关注我们关于注解的下一个章节。
系列:
注解Annotation全解析(一):我不是注释
注解Annotation全解析(二):自定义注解的定义
注解Annotation全解析(三):自定义注解的配置使用
注解Annotation全解析(四):自定义注解的运行时解析
注解Annotation全解析(五):注解与XML的比较