Annotation
[TOC]
Java Annotation
What is annotation
Annotations, a form of metadata, provide data about a program that is not part of the program itself. Annotations have no direct effect on the operation of the code they annotate.
注解(Annotation)是java1.5开始引入的一种注释机制。很多有名的框架都大量使用注解。Java注解用于为Java代码提供元数据,可被编译工具解析,不直接影响代码执行。
Annontation像一种修饰符一样,应用于包、类型、构造方法、方法、成员变量、参数及本地变量的声明语句中。
Architecture

- 每个Annotation对象都有唯一的RetentionPolicy属性
- 每个Annotation对象可以有若干ElementType属性
- Annotation有许多实现类
Grammer
注解通过@interface关键字进行定义
1 | public @interface TestAnnotation{ |
使用
1 |
|
可以理解为将自定义好的@TestAnnotation标签贴到Test类上
但是注解要正常工作 还需要元注解
Meta Annotation
元注解是可以注解到注解上的注解。它是一种基本注解,但它能够应用到其他注解上。
Annotation Type
| 注解类型 | 含义 |
|---|---|
| @Documented | 能够将注解中的元素包含到 Javadoc 中 |
| @Retention | 解释说明了这个注解的的存活时间 |
| @Target | 限定了注解运用场景 |
| @Inherited | 表示注解类型能被自动继承 |
| @Repeatable | 表示可重复 |
@Documented
1 | @Documented |
@Documented表示拥有该注解的元素可通过javadoc此类的工具进行文档化。
该类型应用于注解那些影响客户使用带注释(comment)的元素声明的类型。如果类型声明是用Documented来注解的,这种类型的注解被作为被标注的程序成员的公共API。
例如,上面源码@Retention的定义中有一行@Documented,意思是指当前注解的元素会被javadoc工具进行文档化,那么在查看Java API文档时可查看当该注解元素。
@Retention
1 | @Documented |
| RetentionPolicy | 含义 |
|---|---|
| SOURCE | 注解只在源码阶段保留,在编译器进行编译时它将被丢弃忽视 |
| CLASS | 注解只被保留到编译进行的时候,它并不会被加载到 JVM 中(Default) |
| RUNTIME | 注解可以保留到程序运行的时候,它会被加载进入到 JVM 中,所以在程序运行时可以获取到它们 |
@Retention相当于一个时间戳 指明了标签张贴的时间周期,描述了注解的生命周期。
1 | @Retention(RetentionPolicy。RUNTIME) |
@Target
1 | @Documented |
@Target指定了注解运用的地方,限定了运用的场景
| ElementType | 含义 |
|---|---|
| TYPE | 类、接口(包括注解类型)或enum声明 |
| FIELD | 字段(域)声明,包括enum实例 |
| METHOD | 方法声明 |
| PARAMETER | 参数声明 |
| CONSTRUCTOR | 构造方法声明 |
| LOCAL_VARIABLE | 局部变量声明 |
| ANNOTATION_TYPE | 注解声明(用于另一个注解上) |
| PACKAGE | 包声明 |
| TYPE_PARAMETER | 类型参数声明(since 1.8) |
| TYPE_USE | 类型使用声明(since 1.8) |
若没有@Target元注解则默认可以适用所有的程序元素。
多个值使用需加上{}并用,隔开
1 | @Target(value={CONSTRUCTOR, FIELD, LOCAL_VARIABLE, METHOD, PACKAGE, PARAMETER, TYPE}) |
Type Annotation
TYPE_PARAMETER TYPE_USE
(since Java8)
以前的注解只能用在包、类、构造器、方法、成员变量、参数、局部变量。如果想在创建对象、类型转换、使用implements实现接口、使用throw声明抛出异常时使用则不行。
Example
1 |
|
1 | //implements实现接口中使用Type Annotation |
- 在本地变量上的Annotation可以保留在class文件中
- 泛型类型可以被保留甚至在运行时被访问
Type Annotation为Java程序中的校验提供了更多的可能性。相比于业务方面的校验,它更适用于计算机科学相关的一般性的格式校验。例如:空指针校验,数值范围校验,字符串格式校验等。
@Inherited
1 | @Documented |
@Inherited表示该注解类型被自动继承。
如果用户在当前类中查询这个元注解类型并且当前类的声明中不包含这个元注解类型,那么也将自动查询当前类的父类是否存在Inherited元注解,这个动作将被重复执行知道这个标注类型被找到,或者是查询到顶层的父类。
也就是说,如果一个超类被 @Inherited 注解过的注解进行注解的话,那么如果它的子类没有被任何注解应用的话,那么这个子类就继承了超类的注解。
1 | @Inherited |
注解@Test被@Inherited修饰,类A被@Test修饰,类B继承自A ,于是类B也拥有@Test注解。
@Repeatable
1 |
|
@Repeatable since Java 1.8 作用是使注解的值可以同时取多个
1 | Persons { |
@Repeatable(Persons.class)中Persons.class相当于一个容器注解,即存放其它注解的地方。而Persons中又有一个value属性
相当于Persons是一张总的标签,上面贴artist coder PM等同类型但内容不一样的标签
Annotations applied to Java code
内置注解
@Override
1 | @Target(ElementType.METHOD) |
@Override用于标明此方法覆盖了父类的方法。
可适用元素为方法,仅仅保留在Java源文件中。
@Deprecated
1 | @Documented |
@Deprecated用于标明已经过时的方法或者类。
可适用于除注解类型声明之外的所有元素,保留时长为运行时JVM
@SuppressWarnnings
1 | @Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE}) |
@SuppressWarnnings用于有选择的关闭编译器对类、方法、成员变量、变量初始化的警告。
可适合用于除注解类型声明和包名之外的所有元素,仅仅保留在java源文件中。
内部String数组主要接收值
| 参数 | 含义 |
|---|---|
| deprecation | 使用了过时的类或方法时的警告 |
| unchecked | 执行了未检查的转换时的警告 |
| fallthrough | 当Switch程序块进入进入下一个case而没有Break时的警告 |
| path | 在类路径、源文件路径等有不存在路径时的警告 |
| serial | 当可序列化的类缺少serialVersionUID定义时的警告 |
| finally | 任意finally子句不能正常完成时的警告 |
| all | 以上所有情况的警告 |
@SafeVarargs
@SafeVarargs since Java1.7 参数安全类型注解。目的是处理可变长参数中的泛型 此注解告诉编译器:在可变参数中的泛型是类型安全的
因为泛型的数据类型是运行时才确定的,因此把泛型存储到数组中时,编译器在编译阶段无法检查数据类型是否匹配,会给出警告信息:存在可能的”堆污染“(Heap Pollution) 如果泛型的真实数据类型无法和参数数组的类型匹配 会导致ClassCastException异常 因此在可变长参数中使用泛型时,编译器都会给出警告信息
@FunctionalInterface
@FunctionalInterface since Java1.8 函数式接口注解。函数式接口就是一个具有一个方法的普通接口。
线程开发中常用的Runnable就是典型的函数式接口
1 |
|
函数式接口标记的作用是可以很容易转换为Lambda表达式
Comparison
| 内建注解 | Target | Retention |
|---|---|---|
| Override | METHOD | SOURCE |
| SuppressWarnings | 除ANNOTATION_TYPE和PACKAGE外的所有 | SOURCE |
| Deprecated | 除ANNOTATION_TYPE外的所有 | RUNTIME |
Usage
example
1 | @Documented |
规则
- 注解方法不带参数
- 注解方法返回值类型:基本类型、String、Enums、Annotation以及其数组类型
- 注解方法可有默认值
注解的属性
注解的属性也叫成员变量。注解只有成员变量,没有方法。无形参。
在注解中定义属性时它的类型必须是 8 种基本数据类型外加 类、接口、注解及它们的数组。
TestAnnotation这个注解中拥有username password id 三个属性,在使用时则需要对其进行赋值。
1 | @TestAnnotation(username="a",password="aaaaaa",id=1) |
若TestAnnotation注解只有一个属性,则可不写成员变量名,直接赋值
1 |
|
Theory
Annotation其实是一种接口。Java所有注解都继承了Annotation接口。通过Java的反射机制相关的API来访问Annotation信息。反射类位于包java.lang.reflect,其中有一个接口AnnotatedElement
| 返回值 | 方法 | 含义 |
|---|---|---|
| T | getAnnotation(Class annotationClass) | 当存在该元素的指定类型注解,则返回相应注释,否则返回null |
| Annotation[] | getAnnotations() | 返回此元素上存在的所有注解 |
| Annotation[] | getDeclaredAnnotations() | 返回直接存在于此元素上的所有注解。 |
| boolean | isAnnotationPresent(Class<? extends Annotation> annotationClass) | 当存在该元素的指定类型注解,则返回true,否则返回false |
首先通过Class对象的isAnnotationPresent()判断它是否应用了某个注解,然后通过getAnnotation()来获取Annotation对象 或者是通过getAnnotations()方法。前者返回指定类型的注解 后者返回注解到这个元素上的所有注解。如果获取到的Annotation不为null,则就可以调用它们的属性方法了。
一个注解要在运行时被成功提取,那么必须要@Retention(RetentionPolicy.RUNTIME)
ps:因为注解的提取需要借助于Java反射技术 而反射比较慢 因此使用注解时需要考虑时间成本
Advantage
注解很大程度上是一种辅助工具
- 提供信息给编译器: 编译器可以利用注解来探测错误和警告信息 (确保代码的正确性
- 编译阶段时的处理: 软件工具可以用来利用注解信息来生成代码、Html文档或者做其它相应处理
- 运行时的处理: 某些注解可以在程序运行的时候接受代码的提取
当开发者使用了Annotation 修饰了类、方法、Field 等成员之后,这些 Annotation不会自己生效,必须由开发者提供相应的代码来提取并处理Annotation信息。
1 |
|
1 | private fun realmProcess(roundEnv: RoundEnviironment){ |
对RealmObject类进行@RealmClass注解后,因为所有模型类都要继承RealmObject,因此可以获取所有有@RealmClass注解的类,对每个有这个注解的对象同时进行操作。
Annotation不影响程序代码的执行,无论增加、删除,代码都始终如一的执行。如果希望让程序中的Annotation在运行时起一定的作用,只有通过某种配套工具对Annotation中的信息进行访问和处理。jdk7之前访问和处理Annotation的工具统称APT(Annotation Processing Tool)(jdk7后就被废除了),jdk7及之后采用了JSR 269 API。相关原因官方说明 和 原因。
Android Annotation
Android Support Library
Android Support Library 提供了很多实用注解。
Examples
实例化控件
1 | @Bind(R.id.btn) |
设置控件点击事件
1 | @Click(R.id.btn) |
在Android项目中使用Annotation 减少代码量 使代码更优雅
但是缺点就是反射会降低程序的运行速度 长期的话难以维护
Android APT
APT(Annotation Processing Tool)
How to write an annotation processor
how do you debug java annotation processors using intellij?
参考链接:
拓展阅读: