MOOC 个人学习笔记

# 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 类型
    • 注解类型
    • 由前面类型组成的数组
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
public @interface Test {
    
}
  • @SingleTest
  • @SingleTest(5)
  • @SingleTest(value=5)
public @interface SingleTest {
    int value() default 0;
}
  • @MultipleTest
  • @MultipleTest(a=1)
  • @MultipleTest(a=1,b=2)
  • @MultipleTest(b=2,a=1)
  • @MultipleTest (1,2) 是错误写法
public @interface MultipleTest {
    int a() default 0;
    int b() default 0;
}

# 注解使用的位置

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

# 注解作为单元测试

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 引入
  • 表示被修饰的注解可以重复应用标注
  • 需要定义注解和容器注解
// 普通注解
@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 中,访问速度快