# 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