# Java5 的注解

注解就相当于一种标记,在程序中加了注解就等于为程序加了某种标记。告诉 javac 编译器或者 java 开发工具…… 向其传递某种信息,作为一个标记。

同时注解分为三个阶段(注解的生命周期): java 源文件 --> class 文件 --> 内存中的字节码。由元注解 Retention 决定,注解的默认阶段是 Class

注解阶段
@Retention(RetentionPolicy.SOURCE)源文件
@Retention(RetentionPolicy.CLASS)class 文件
RetentionPolicy.RUNTIME内存中的字节码

# Java8 的注解

Java8 主要是两点改进:类型注解和重复注解。

# 类型注解

java 8 之前,注解只能是在声明的地方所使用,比如类,方法,属性; java 8 里面,注解可以应用在任何地方。

// 创建类实例
new @Interned MyObject();	
// 类型映射
myString = (@NonNull String) str;	
//implements 语句
class UnmodifiableList<T> implements @Readonly List<@Readonly T> {}
//throw exception 声明
void monitorTemperature() throws @Critical TemperatureException {}

类型注解只是语法而不是语义,并不会影响 java 的编译时间,加载时间,以及运行时间,也就是说,编译成 class 文件的时候并不包含类型注解。

在枚举类型 ElementType 中新增了两个

// Type parameter declaration
// @since 1.8 
// 表示该注解能写在类型变量的声明语句中
TYPE_PARAMETER,
// Use of a type
// @since 1.8
// 表示该注解能写在使用类型的任何语句中
TYPE_USE

Java8 并没有集成关于类型注解的使用框架,它需要开发者自己去搭建框架来使用,比如华盛顿大学开发出来的第三方插件工具 Cheker Framework。类型注解被用来支持在 Java 程序中做强类型检查,配合上述工具可以在编译时检测出 runtime error

注意:使用 Checker Framework 可以找到类型注解出现的地方并检查。

import checkers.nullness.quals.*;
public class TestDemo{
    void sample() {
        @NonNull Object my = new Object();
    }
}
// 使用 javac 编译,上述是通过的
javac -processor checkers.nullness.NullnessChecker TestDemo.java
// 但是改为下面代码就不会通过
@NonNull Object my = null;

注意 java 5,6,7 版本是不支持注解 @NonNul l,但 checker framework 有个向下兼容的解决方案,就是将类型注解 @NonNull 用 /**/ 注释起来。

import checkers.nullness.quals.*;
public class TestDemo{
    void sample() {
        /*@NonNull*/ Object my = null;
    }
}

有时间我再出一篇关于 Checker Framework 的使用文章吧,确实很简单,就是官网是英文的让我这个英语天坑看着难受。

# 关于 JSR 308

你可以就把 JSR 308 与类型注解挂钩。

JSR 308 想要解决在 Java 1.5 注解中出现的两个问题:

  • 在句法上对注解的限制:只能把注解写在声明的地方
  • 类型系统在语义上的限制:类型系统还做不到预防所有的 bug

JSR 308 通过如下方法解决上述两个问题:

  • 对 Java 语言的句法进行扩充,允许注解出现在更多的位置上。包括:方法接收器 (method receivers,译注:例 public int size() @Readonly { … }) ,泛型参数,数组,类型转换,类型测试,对象创建,类型参数绑定,类继承和 throws 子句。其实就是类型注解,现在是 java 8 的一个特性
  • 通过引入可插拔的类型系统 (pluggable type systems) 能够创建功能更强大的注解处理器。类型检查器对带有类型限定注解的源码进行分析,一旦发现不匹配等错误之处就会产生警告信息。其实就是 check framework

对 JSR308,有人反对,觉得更复杂更静态了,比如

@NotEmpty List<@NonNull String> strings = new ArrayList<@NonNull String>()>

换成动态语言为

var strings = ["one", "two"];

有人赞成,说到底,代码才是 “最根本” 的文档。代码中包含的注解清楚表明了代码编写者的意图。当没有及时更新或者有遗漏的时候,恰恰是注解中包含的意图信息,最容易在其他文档中被丢失。而且将运行时的错误转到编译阶段,不但可以加速开发进程,还可以节省测试时检查 bug 的时间。

关于 JSR 308 的争议,可以看这篇文章:JSR 308:Java 语言复杂度在恣意增长?

# 重复注解

允许在同一申明类型 (类,属性,或方法) 的多次使用同一个注解。

JDK1.8 之前使用重复注解的解决方案

// 注解
public @interface Authority {
     String role();
}
public @interface Authorities {
    Authority[] value();
}
public class RepeatAnnotationUseOldVersion {
    @Authorities({@Authority(role="Admin"),@Authority(role="Manager")})
    public void doSomeThing(){
    }
}

由另一个注解来存储重复注解,在使用时候,用存储注解 Authorities 来扩展重复注解。

Java8 的做法:

@Repeatable(Authorities.class)
public @interface Authority {
     String role();
}
public @interface Authorities {
    Authority[] value();
}
public class RepeatAnnotationUseNewVersion {
    @Authority(role="Admin")
    @Authority(role="Manager")
    public void doSomeThing(){ }
}

创建重复注解 Authority 时,加上 @Repeatable , 指向存储注解 Authorities ,在使用时候,直接可以重复使用 Authority 注解。从上面例子看出, java 8 里面做法更适合常规的思维,可读性强一点。

# 参考

Java 全栈知识体系:https://pdai.tech/md/java/java8/java8-type-anno.html

Java 全栈知识体系:https://pdai.tech/md/java/java8/java8-anno-repeat.html

冰河的博客:https://www.cnblogs.com/binghe001/p/13033447.html