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

image

  • 每个Annotation对象都有唯一的RetentionPolicy属性
  • 每个Annotation对象可以有若干ElementType属性
  • Annotation有许多实现类

Grammer

注解通过@interface关键字进行定义

1
2
public @interface TestAnnotation{
}

使用

1
2
3
@TestAnnotation
public class Test{
}

可以理解为将自定义好的@TestAnnotation标签贴到Test类上

但是注解要正常工作 还需要元注解

Meta Annotation

元注解是可以注解到注解上的注解。它是一种基本注解,但它能够应用到其他注解上。

Annotation Type

注解类型 含义
@Documented 能够将注解中的元素包含到 Javadoc 中
@Retention 解释说明了这个注解的的存活时间
@Target 限定了注解运用场景
@Inherited 表示注解类型能被自动继承
@Repeatable 表示可重复

@Documented

1
2
3
4
5
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Documented {
}

@Documented表示拥有该注解的元素可通过javadoc此类的工具进行文档化。

该类型应用于注解那些影响客户使用带注释(comment)的元素声明的类型。如果类型声明是用Documented来注解的,这种类型的注解被作为被标注的程序成员的公共API。

例如,上面源码@Retention的定义中有一行@Documented,意思是指当前注解的元素会被javadoc工具进行文档化,那么在查看Java API文档时可查看当该注解元素。

@Retention

1
2
3
4
5
6
@Documented
@Retention((RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Retention {
RetentionPolicy value();
}
RetentionPolicy 含义
SOURCE 注解只在源码阶段保留,在编译器进行编译时它将被丢弃忽视
CLASS 注解只被保留到编译进行的时候,它并不会被加载到 JVM 中(Default)
RUNTIME 注解可以保留到程序运行的时候,它会被加载进入到 JVM 中,所以在程序运行时可以获取到它们

@Retention相当于一个时间戳 指明了标签张贴的时间周期,描述了注解的生命周期。

1
2
3
4
@Retention(RetentionPolicy。RUNTIME)
public @interface TestAnnotation{

}

@Target

1
2
3
4
5
6
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Target {
ElementType[] value();
}

@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
2
3
4
5
6
@Target(ElementType.TYPE_USE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface NotNull {
String value() default "";
}
1
2
3
4
5
6
7
8
9
10
11
//implements实现接口中使用Type Annotation
public class Test implements @NotNull(value="Serializable") Serializable{

//泛型中使用Type Annotation 、 抛出异常中使用Type Annotation
public void foo(List<@NotNull String> list) throws @NotNull(value="ClassNotFoundException") ClassNotFoundException {
//创建对象中使用Type Annotation
Object obj =new @NotNull String("annotation.Test");
//强制类型转换中使用Type Annotation
String str = (@NotNull String) obj;
}
}
  • 在本地变量上的Annotation可以保留在class文件中
  • 泛型类型可以被保留甚至在运行时被访问

Type Annotation为Java程序中的校验提供了更多的可能性。相比于业务方面的校验,它更适用于计算机科学相关的一般性的格式校验。例如:空指针校验,数值范围校验,字符串格式校验等。

@Inherited

1
2
3
4
5
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Inherited {
}

@Inherited表示该注解类型被自动继承。

如果用户在当前类中查询这个元注解类型并且当前类的声明中不包含这个元注解类型,那么也将自动查询当前类的父类是否存在Inherited元注解,这个动作将被重复执行知道这个标注类型被找到,或者是查询到顶层的父类。

也就是说,如果一个超类被 @Inherited 注解过的注解进行注解的话,那么如果它的子类没有被任何注解应用的话,那么这个子类就继承了超类的注解。

1
2
3
4
5
6
7
8
9
10
@Inherited
@Retention(RetentionPolicy.RUNTIME)
@interface Test {}


@Test
public class A {}


public class B extends A {}

注解@Test@Inherited修饰,类A@Test修饰,类B继承自A ,于是类B也拥有@Test注解。

@Repeatable

1
2
3
4
5
6
7
8
9
10
11
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Repeatable {
/**
* Indicates the <em>containing annotation type</em> for the
* repeatable annotation type.
* @return the containing annotation type
*/
Class<? extends Annotation> value();
}

@Repeatable since Java 1.8 作用是使注解的值可以同时取多个

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@interface Persons {
Person[] value();
}

@Repeatable(Persons.class)
@interface Person{
String role default "";
}

@Person(role="artist")
@Person(role="coder")
@Person(role="PM")
public class SuperMan{
}

@Repeatable(Persons.class)Persons.class相当于一个容器注解,即存放其它注解的地方。而Persons中又有一个value属性

相当于Persons是一张总的标签,上面贴artist coder PM等同类型但内容不一样的标签

Annotations applied to Java code

内置注解

@Override

1
2
3
4
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public @interface Override {
}

@Override用于标明此方法覆盖了父类的方法。

可适用元素为方法,仅仅保留在Java源文件中。

@Deprecated

1
2
3
4
5
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(value={CONSTRUCTOR, FIELD, LOCAL_VARIABLE, METHOD, PACKAGE, PARAMETER, TYPE})
public @interface Deprecated {
}

@Deprecated用于标明已经过时的方法或者类。

可适用于除注解类型声明之外的所有元素,保留时长为运行时JVM

@SuppressWarnnings

1
2
3
4
5
@Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE})
@Retention(RetentionPolicy.SOURCE)
public @interface SuppressWarnings {
String[] value();
}

@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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@FunctionalInterface
public interface Runnable {
/**
* When an object implementing interface <code>Runnable</code> is used
* to create a thread, starting the thread causes the object's
* <code>run</code> method to be called in that separately executing
* thread.
* <p>
* The general contract of the method <code>run</code> is that it may
* take any action whatsoever.
*
* @see java.lang.Thread#run()
*/
public abstract void run();
}

函数式接口标记的作用是可以很容易转换为Lambda表达式

Comparison

内建注解 Target Retention
Override METHOD SOURCE
SuppressWarnings 除ANNOTATION_TYPE和PACKAGE外的所有 SOURCE
Deprecated 除ANNOTATION_TYPE外的所有 RUNTIME

Usage

example

1
2
3
4
5
6
7
8
9
@Documented
@Target(ElementType.METHOD)
@Inherited
@Retention(RetentionPolicy.RUNTIME)
public @interface TestAnnotation{
String username();
String password();
int id() default 0;
}

规则

  1. 注解方法不带参数
  2. 注解方法返回值类型:基本类型、String、Enums、Annotation以及其数组类型
  3. 注解方法可有默认值

注解的属性

注解的属性也叫成员变量。注解只有成员变量,没有方法。无形参。

在注解中定义属性时它的类型必须是 8 种基本数据类型外加 类、接口、注解及它们的数组。

TestAnnotation这个注解中拥有username password id 三个属性,在使用时则需要对其进行赋值。

1
2
3
@TestAnnotation(username="a",password="aaaaaa",id=1)
public class Test{
}

若TestAnnotation注解只有一个属性,则可不写成员变量名,直接赋值

1
2
3
@TestAnnotation(1)
public class Test{
}

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
2
3
@RealmClass
public abstract class RealmObject implements RealmModel{
}
1
2
3
4
5
private fun realmProcess(roundEnv: RoundEnviironment){
for(classElement in rounndEnv.getElementsAnnotatedWith(RealmClass::class.java)){
generate(classElement as TypeElement)
}
}

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
2
@Bind(R.id.btn)
Button mBtn;

设置控件点击事件

1
2
3
@Click(R.id.btn)
void btnClick(){
}

在Android项目中使用Annotation 减少代码量 使代码更优雅

但是缺点就是反射会降低程序的运行速度 长期的话难以维护

Android APT

APT(Annotation Processing Tool)

How to write an annotation processor

ANNOTATION PROCESSING 101

how do you debug java annotation processors using intellij?

参考链接:

拓展阅读: