Java 注解一

来源:互联网 发布:递归算法理解 编辑:程序博客网 时间:2024/06/06 03:24

使用 JetBrains 注解库注释你的代码

依赖

  • annotations-java8.jar
    这个包在任何一个 JetBrains IDE 的安装目录里面都有,在 kotlin-runtime.jar 里面也有,在 maven 仓库也有。
    为什么需要它

首先我们知道 Kotlin 解决了一个万年大问题—— null 。 几乎每个代码量超过两千的项目,先做出原型后,前两次运行或单元测试,都死在 NullPointerException 上。 Kotlin 从根本上解决了这个问题——它要求你在编译期处理所有可能为 null 的情况,这是语言级别的支持,比C#那种只提供一个语法糖的半吊子好得多。
但是反观 Java ,就没这玩意。 因此,伟大的 JetBrains 就搞了个注解库,然后通过在 IDE 里面提示你处理那些可能为 null 的值(毕竟你没法直接让编译器自己检查并提示)来解决这个问题 (其实人家自己早就在用了,只是 Kotlin 也用到了这个注解库而已)。
我写的全限定名,因为把包名也写出来可以方便读者区分它和另外一套同名注解(sun的垃圾玩意)。
第一个注解, @TestOnly

@org.jetbrains.annotations.TestOnly

这个说都不用说了,就是专门写给单元测试服务的相关代码的注解。比如我的算法库,在单元测试端我写了对应的暴力算法来验证我的算法的正确性(这种方法在 OI 中称为对拍),那么这几个暴力算法的实现就适合使用 @TestOnly 注解修饰。

/** * brute force implementation of binary indexed tree. */private inner class BruteForce@TestOnlyinternal constructor(length: Int) {    @TestOnly    private val data = LongArray(length)    @TestOnly    fun update(from: Int, to: Int, value: Long) {        (from..to).forEach { data[it] += value }    }    @TestOnly    fun query(from: Int, to: Int): Long {        var ret = 0L        (from..to).forEach { ret += data[it] }        return ret    }    @TestOnly    operator fun get(left: Int, right: Int) = query(left, right)}

第二、三个注解, @NotNull 和 @Nullable

@org.jetbrains.annotations.NotNull@org.jetbrains.annotations.Nullable

这个很简单,顾名思义。它们一般被用来注解带有返回值的方法、方法参数、类的成员变量。
当 @NotNull 注解一个方法参数的时候, IDE 会在调用处提示你处理 null 的情况(当然,如果 IDE 语义上认为你传进去的参数不可能是 Null ,那么当然没有提示了); 当它注解一个有返回值的方法的时候,它会检查返回值是否可能是 null 。如果可能,那也会有提示。
当 @Nullable 注解一个方法参数的时候, IDE 会在方法内部提示你处理该参数为 null 的情况; 当它注解一个有返回值的方法的时候,会在调用处提示你处理方法返回值为 null 的情况。
相应的,任何以 @NotNull 修饰的变量/属性/方法,它在 Kotlin 中对应的类型都写作它本身,即非 null 类型,任何以 @Nullable 修饰的变量/属性/方法,它在 Kotlin 中对应的类型都写作它本身后面跟一个问号,即可能为 null 的类型。 这和 Java 的区别在于, Kotlin 编译器强制你处理 null , Java 只有 IDE 警告。 另外,这个注解本身的命名也是一种警告,不过是对于开发者而言的。
第四、五个注解, @Nls 和 @NonNls

@org.jetbrains.annotations.Nls@org.jetbrains.annotations.NonNls

这是两个十分有意思的注解,用于修饰字符串,而且是写给人看的,这个就不是给 IDE 看的辣。 @Nls 用于修饰自然语言字符串,比如下面这个例子。 假设 textArea 是一个 JTextArea ,是一个游戏画面用于显示一些提示信息的框框。 这些提示信息当然是自然语言了。
所以就可以这样修饰:

public void boyNextDoor(@Nls String msg) {    textArea.append(msg);}

或者用于程序的 Crash 信息:

public DividedByZeroException(@NotNull @Nls String msg) {    super(msg);}

然后阅读代码的人就知道,哦,这个 msg 里面是一些自然语言,比如什么”My name is Van, I’m an artist.”之类的。
反之, @NonNls 就是用于修饰非自然语言。 比如我的算法库有一个大整数类,其中有一个构造方法接收一个字符串,然后这个大整数就从字符串构造。 这个字符串一般长这样: “23333333333333333333333333” 或者: “-6666666666666666666666666666666”。
这显然不是自然语言!于是:

public BigInt(@NotNull @NonNls String origin) {    boolean sigTemp;    if (origin.startsWith("-")) {        sigTemp = false;        origin = origin.substring(1);    } else sigTemp = true;    byte[] ori = origin.getBytes();    if (ori.length == 1 && ori[0] == '0') sigTemp = true;    data = ori;    sig = sigTemp;}

最后的注解 @Contract

@org.jetbrains.annotations.Contract

我已经在上一篇博客提到了这个神奇的 @Contract 注解。事实上,这个注解能描述的内容更详细,还能在一定程度上代替 @NotNull 和 @Nullable 。
这是一个有值的、用于修饰那些参数数量大于零并且返回值不为 void 的普通方法和构造方法的注解,比起 @NotNull 和 @Nullable ,它相对来说更复杂。因此,首先我们得看看它的源码。

package org.jetbrains.annotations;import java.lang.annotation.*;@Documented@Retention(RetentionPolicy.CLASS)@Target({ElementType.METHOD, ElementType.CONSTRUCTOR})public @interface Contract {    String value() default "";    boolean pure() default false;}

可以看到,这个注解类有两个属性。首先有一个 value 的字符串属性,还有一个 pure 的布尔属性。
首先从容易理解的 pure 说起吧。
pure 属性

这个属性听名字都能猜出什么意思——表示被注解的函数(就是方法,包含普通方法、静态方法和构造方法,下同)是否为纯函数。
纯函数(好像)是一个 fp 里面的概念,如果一个函数,对于特定的输入,都将产生对应的唯一的输出,并且不会影响别的东西(即没有副作用),那么这个函数就是纯函数。 数学上的函数就是最标准的纯函数,比如我有一个 f(x) ,那么对应每一个 x ,这个函数的输出都有一个对应的 f(x) ,并且我多次输入同一个 x ,输出的 f(x) 也是同一个。 这就是一个纯函数。
举个反例,下面是一段完整的(即可以编译运行的) C 语言代码,这里的函数 not_Pure 就不是一个纯函数。

#include <stdio.h>int not_Pure(int assWeCan) {    static int boyNextDoor = 233;    return ++boyNextDoor * assWeCan;}int main(const int argc, const char *argv[]) {    printf("%d\n", not_Pure(5));    printf("%d\n", not_Pure(5));    printf("%d\n", not_Pure(5));    printf("%d\n", not_Pure(5));    printf("%d\n", not_Pure(5));    return 0;}

我在 main 函数里面对 not_Pure 进行了五次调用,每次传进去的参数都是 5 ,但是它却产生了这样的输出:
1170
1175
1180
1185
1190
好。话题回到 @Contract 上。那么这个 pure 值怎么使用呢?
当你的函数是一个纯函数时(比如三角函数运算,这是再简单不过的纯函数了),你就可以这样修饰。

 @Contract(pure = true)    public static native double sin(double a);    @Contract(pure = true)    public static native double cos(double a);    @Contract(pure = true)    public static native double tan(double a);    @Contract(pure = true)    public static native double cot(double a);    @Contract(pure = true)    public static native double csc(double a);    @Contract(pure = true)    public static native double sec(double a);

再比如我算法库大整数的 JNI 端和 Java 端,加减乘除和比大小都不会影响原来的两个算子,会新分配一块内存来放置运算结果,那么这些函数也统统可以使用 @Contract(pure = true)注解。
value 属性

这是 @Contract 注解的精髓,应用场景也非常好说明。 首先考虑一个函数——大整数类的 equals 。 我当然是需要处理一些特殊情况的,比如如果你传进去了一个 null ,那么我返回 true 。于是按照 Java 标准库那个注释思路,我们应该这样写:

/** * This is a pure function. * returns true if the value of given * parameter is equaled to the caller. * * if obj is null, it will return false. * if obj is not a org.algo4j.BigInt, it will return false. * * @param obj the given obj * @returns if obj is equaled to the caller */@Overridepublic boolean equals(@Nullable Object obj) {    if (obj == null || !(obj instanceof BigInt)) return false;    if (obj == this) return true;    return compareTo((BigInt) obj) == 0;}

但是,仔细想想,其实你只是需要告诉用户,要是你给我 null ,我就还你 false 。 使用一大坨自然语言描述这个简单逻辑,不仅会让人看很久,而且如果注释丢了,用户也就无从得知这个情况了,而且这对于英语不好的人非常不友好。
于是我们可以这样写:

@Override@Contract(value = "null -> false", pure = true)public boolean equals(@Nullable Object obj) {    if (obj == null || !(obj instanceof BigInt)) return false;    if (obj == this) return true;    return compareTo((BigInt) obj) == 0;}

首先我们省去了一大堆注释,并且使用了一个字符串描述这个逻辑: “null -> false” 。意思就是,给我一个 null ,还你一个 false 。
这个字符串由两部分组成,箭头前面的是参数说明,后面是返回值说明。如果有多种需要说明的情况,那么使用分号隔开。
一共有这么几个允许使用的值:
null // null
!null // not null
true // boolean value true
false // boolean value falses
fail // means this function will not work in this case
_ // any value
一共六个,不要忽略了最后一个下划线,它的含义和 Scala 中的下划线相同——表示通配符。比如这个扩展欧几里得函数:

@NotNull@Contract(value = "_, _ -> !null", pure = true)public static ExgcdRes exgcd(long a, long b) {    return new ExgcdRes(exgcdJni(a, b));}

直接两个注解说明一切:首先返回值为非 null ,因此 @NotNull 。 然后,不论你传进来任何值,我都不会返回 null 的,因此 @Contract(“, -> !null”) 。 最后,它是一个纯函数,因此 pure = true 。综上,有了这个函数的注解:
@NotNull
@Contract(value = “, -> !null”, pure = true)
我那几个大整数加减乘除的方法就可以这样写了:
本地接口:

@NotNull@Contract(value = "!null, !null -> !null", pure = true)private static native byte[] plus(@NotNull byte[] a, @NotNull byte[] b);@NotNull@Contract(value = "!null, !null -> !null", pure = true)private static native byte[] times(@NotNull byte[] a, @NotNull byte[] b);@NotNull@Contract(value = "!null -> !null", pure = true)private static native byte[] times10(@NotNull byte[] a);@NotNull@Contract(value = "!null, !null -> !null", pure = true)private static native byte[] divide(@NotNull byte[] a, @NotNull byte[] b);@NotNull@Contract(value = "!null, !null -> !null", pure = true)private static native byte[] minus(@NotNull byte[] a, @NotNull byte[] b);@NotNull@Contract(value = "!null, _ -> !null", pure = true)private static native byte[] pow(@NotNull byte[] a, int pow);@Contract(pure = true)private static native int compareTo(@NotNull byte[] a, @NotNull byte[] b);调用:@NotNull@Contract(value = "_ -> !null", pure = true)public BigInt plus(@NotNull BigInt anotherBigInt) {    if (sig == anotherBigInt.sig)        return new BigInt(plus(data, anotherBigInt.data), sig);    if (compareTo(data, anotherBigInt.data) > 0)        return new BigInt(minus(data, anotherBigInt.data), sig);    return new BigInt(minus(anotherBigInt.data, data), !sig);}@NotNull@Contract(value = "_ -> !null", pure = true)public BigInt minus(@NotNull BigInt anotherBigInt) {    if (sig != anotherBigInt.sig)        return new BigInt(plus(data, anotherBigInt.data), sig);    if (compareTo(data, anotherBigInt.data) > 0)        return new BigInt(minus(data, anotherBigInt.data), sig);    return new BigInt(minus(anotherBigInt.data, data), !sig);}@NotNull@Contract(value = "_ -> !null", pure = true)public BigInt times(@NotNull BigInt anotherBigInt) {    return new BigInt(times(data, anotherBigInt.data), sig == anotherBigInt.sig);}