少女祈祷中...

1.注解基础

  • 从JDK 1.5 引入
  • 位于源码中(代码/注释/注解),使用其他工具进行处理的标签
  • 注解用来修饰程序的元素,但不会对被修饰的对象有直接的影响
  • 只有通过某种配套的工具才会对注解信息进行访问和处理
  • 主要用途
    • 提供信息给编译器/IDE工具
    • 可用于其他工具来产生额外的代码/配置文件等
    • 有一些注解可在程序运行时访问,增加程序的动态性

JDK预定义的普通注解(部分)

  • @Override 表示继承和改写 自带注解
  • @Deprecated 表示废弃 自带注解
  • @SuppressWarnings 表示压制警告 自带注解
  • @SafeVarargs 不会对不定项参数做危险操作 自带注解
  • @FunctionInterface 声明功能性接口 自带注解

JDK预定义的元注解(部分)

  • @Target 设置目标范围 元注解
  • @Retention 设置保持性 元注解
  • @Documented 文档 元注解
  • @Inherited 注解继承 元注解
  • @Repeatable 此注解可以重复修饰 元注解

2.预定义的普通注解

@Override

  • 修饰方法,检查该方法是父类的方法
  • 强制该函数代码必须符合父类中该方法的定义
  • 避免代码错误

@Deprecated

  • 修饰类/类的元素/包
  • 标注为废除,建议程序员不再使用这个类/元素/包

@SuppressWarnings

  • 可以修饰变量/方法/构造函数/类等
  • 压制各种不同类型的警告信息,使得编译器不显示警告
  • 各种不同类型是叠加,如修饰类的警告类型,和修饰方法的警告类型,对于方法来说,是叠加的
  • 警告类型名称是编译器/IDE工具自己定的,Java规范没有强制要求哪些名称。编译器厂商需要自行协商,保证同名警告类型在各个编译器上一样工作
  • @SuppressWarnings
    • @SuppressWarnings(“unchecked”) 忽略unchecked警告信息
    • @SuppressWarnings(“deprecated”) 忽略过时方法的警告信息
    • @SuppressWarnings({“unchecked”,“deprecated”}) 忽略两种警告信息
    • @ SuppressWarnings(values={“unchecked”,“deprecated”}) 同上
    • @ SuppressWarnings( “all”) 忽略所有的警告信息
  • JLS 只规定了deprecated和unchecked两种
  • 其他的警告类型
    • all,忽略所有的警告
    • cast,忽略类转型警告
    • serial,忽略实现Serializable接口的,没有定义serialVersionUID
    • 使用javac -X 可以看当前的编译器使用哪些警告类型

3.自定义注解

  • 注解定义:扩展java.lang.annotation.Annotation注解接口
  • 注解可以包括的类型
    • 8种基本类型(int/short/long/float/double/byte/char/boolean) –String
    • Class
    • enum类型
    • 注解类型
    • 由前面类型组成的数组
1
2
3
4
5
6
7
public @interface BugReport {
enum Status {UNCONFIRMED, CONFIRMED, FIXED, NOTABUG};
boolean showStopper() default true;
String assiganedTo() default "[none]";
Status status() default Status.UNCONFIRMED;
String[] reportedBy();
}

注解使用

  • @Test
1
2
3
public @interface Test {

}
  • @SingleTest
  • @SingleTest(5)
  • @SingleTest(value=5)
1
2
3
public @interface SingleTest {
int value() default 0;
}
  • @MultipleTest
  • @MultipleTest(a=1)
  • @MultipleTest(a=1,b=2)
  • @MultipleTest(b=2,a=1)
  • @MultipleTest(1,2)是错误写法
1
2
3
4
public @interface MultipleTest {
int a() default 0;
int b() default 0;
}

注解使用的位置

  • @Target可以限定位置
1
2
3
4
@Retention(RetentionPolicy.RUNTIME)
//表示该注解会保留在class文件中
@Target(ElementType.METHOD)
//表示该注解只能用于方法
  • 允许的位置
    • 接口
    • 方法
    • 构造器
    • 成员变量
    • 局部变量/形参变量/类型参数

注解作为单元测试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
public class Foo {
@SingleTest(1)
public static void m1(int a) {
if(a<0)
{
throw new RuntimeException("Crash");
}
}

public static void m2() {
}

@SingleTest(value=-2)
public static void m3(int a) {
if(a<0)
{
throw new RuntimeException("Crash");
}
}
}

public static void main(String[] args) throws Exception {
int passed = 0, failed = 0;
String className = "annotations.single.Foo";
for (Method m : Class.forName(className).getMethods()) {

if (m.isAnnotationPresent(SingleTest.class)) {
System.out.println(m.getName());
SingleTest st = m.getAnnotation(SingleTest.class);
try {
m.invoke(null,st.value());
passed++;
} catch (Throwable ex) {
System.out.printf("Test %s failed: %s %n", m, ex.getCause());
failed++;
}
}
}
System.out.printf("Passed: %d, Failed %d%n", passed, failed);
}

4.预定义的元注解

Retention

  • 示例@Retention(RetentionPolicy.RUNTIME)
  • 这个注解用来修饰其他注解的存在范围
  • RetentionPolicy.SOURCE 注解仅存在源码,不在class文件
  • RetentionPolicy.CLASS 默认的注解保留策略 注解存在于.class文件,但是不能被JVM加载
  • RetentionPolicy.RUNTIME 这种策略下,注解可以被JVM运行时访问到。通常情况下,可以结合反射来做一些事情

Target

  • 限定目标注解作用于什么位置@Target({ElementType.METHOD})
  • ElementType.ANNOTATION_TYPE(注:修饰注解)
  • ElementType.CONSTRUCTOR
  • ElementType.FIELD
  • ElementType.LOCAL_VARIABLE
  • ElementType.METHOD
  • ElementType.PACKAGE
  • ElementType.PARAMETER
  • ElementType.TYPE(注:任何类型,即上面的的类型都可以修饰)

Inherited

  • 让一个类和它的子类都包含某个注解
  • 普通的注解没有继承功能

Repeatable

  • 自JDK1.8引入
  • 表示被修饰的注解可以重复应用标注
  • 需要定义注解和容器注解
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//普通注解
@Retention(RetentionPolicy.RUNTIME)
@Repeatable(RepeatableAnnotations.class)
public @interface RepeatableAnnotation {

int a() default 0;
int b() default 0;
int c() default 0;
}

//容器注解
@Retention(RetentionPolicy.RUNTIME)
public @interface RepeatableAnnotations {
RepeatableAnnotation[] value();
}

Documented

  • 指明这个注解可以被Javadoc工具解析,形成帮助文档

5.注解的解析

RetentionPolicy.RUNTIME

  • 注解在class文件中,被JVM加载,可用反射解析注解
    • Class.getAnnotations()
    • Class.isAnnotation()
    • Class. .isAnnotationPresent(Class annotationClass)
    • Method.getAnnotations()
    • Method.isAnnotationPresent(Class annotationClass)
    • Field.getAnnotations()
    • Field.isAnnotationPresent(Class annotationClass)
    • Constructor.getAnnotations()
    • Constructor.isAnnotationPresent(Class annotationClass)

RetentionPolicy.CLASS

  • 注解在class文件中,但JVM没有加载
  • 只能采用字节码工具进行特殊处理

RetentionPolicy.SOURCE

  • 注解在java文件中,不在class文件中,也不会被JVM加载
  • 只有在源码级别进行注解处理
  • Java提供注解处理器来解析带注解的源码,产生新的文件
    • 注解处理器继承AbstractProcessor,重写process方法
    • javac –processor Processor1, Processor2, … sourceJavaFile
    • 编译器定位源文件的注解,然后依次启动注解处理器执行处理。如果某个注解处理器产生新的源文件,那么将重复执行这个处理过程。
    • 注解处理器只能产生新文件,不会修改已有的源文件

Java 5/6提供的APT工具

6.RUNTIME注解的实现本质

  • 注解采用接口中的方法来表示变量
  • Java为注解产生一个代理类。这个代理类包括一个AnnotationInvocationHandler成员变量
  • AnnotationInvocationHandler有一个Map的成员变量,用来存储所有的注解的属性赋值
  • 在程序中,调用注解接口的方法,将会被代理类接管,然后根据方法名字,到Map里面拿相应的Value并返回
  • 传统的接口中的变量,都是public final static
  • 注解需要随意赋值
    • 注解方法表示变量
    • 采用代理类拦截注解方法访问
    • 所有的注解的赋值,都放在Map中,访问速度快