如何更有效地使用@Nullable 和@Nonnull 注释?

我可以看到,@Nullable@Nonnull注释 可以有助于防止 NullPointerException,但他们不传播很远。

  • 这些注释的有效性在一个间接级别之后就完全下降了,所以如果您只添加一些注释,它们不会传播很远。
  • 由于这些注释没有得到很好的执行,因此有可能假设标记为 @Nonnull的值不为 null,从而不执行 null 检查。

下面的代码使标记为 @Nonnull的参数变为 null,而不会引起任何投诉。它运行时抛出一个 NullPointerException

public class Clazz {
public static void main(String[] args){
Clazz clazz = new Clazz();


// this line raises a complaint with the IDE (IntelliJ 11)
clazz.directPathToA(null);


// this line does not
clazz.indirectPathToA(null);
}


public void indirectPathToA(Integer y){
directPathToA(y);
}


public void directPathToA(@Nonnull Integer x){
x.toString(); // do stuff to x
}
}

有没有办法使这些注释得到更严格的执行和/或进一步传播?

218380 次浏览

简短的回答: 我想这些注释只对您的 IDE 有用,它们可以警告您可能出现的空指针错误。

正如“清理代码”一书中所说,您应该检查公共方法的参数,并避免检查不变量。

另一个好的提示是从不返回 null 值,而是使用 空对象模式

在 Java 中我会使用 番石榴的可选类型。作为一个实际的类型,您可以得到编译器对其使用的保证。绕过它获得 NullPointerException很容易,但至少该方法的签名清楚地传递了它期望作为参数的内容或它可能返回的内容。

除了在向期望参数不为空的方法传递 null时 IDE 会给出提示之外,还有其他一些好处:

  • 静态程序分析工具可以测试与 IDE 相同的东西(例如: FindBugs)
  • 可以使用 面向侧面的程序设计检查这些断言

这可以帮助您的代码更易于维护(因为您不需要 null检查)和更少的错误倾向。

我同意注释“不会传播很远”。但是,我看到程序员的错误。

我将 Nonnull注释理解为文档。下面的方法表示需要(作为先决条件)一个非空参数 x

    public void directPathToA(@Nonnull Integer x){
x.toString(); // do stuff to x
}

然后,下面的代码片段包含一个 bug。该方法调用 directPathToA()而不强制 y为非空(也就是说,它不保证被调用方法的前提条件)。一种可能性是向 indirectPathToA()添加一个 Nonnull注释(传播前置条件)。可能性二是检查 indirectPathToA()中的 y是否为空,并避免在 y为空时调用 directPathToA()

    public void indirectPathToA(Integer y){
directPathToA(y);
}

在遵从性1.8编译 Eclipse 中的原始示例时,如果启用了基于注释的 null 分析,我们会得到以下警告:

    directPathToA(y);
^
Null type safety (type annotations): The expression of type 'Integer' needs unchecked conversion to conform to '@NonNull Integer'

此警告的措辞类似于使用原始类型(“未检查的转换”)将通用代码与遗留代码混合时得到的警告。我们在这里有完全相同的情况: 方法 indirectPathToA()有一个“遗留”签名,因为它没有指定任何空契约。工具可以很容易地报告这一点,因此它们会在需要传播 null 注释但尚未传播的所有小巷中追踪您。

当使用一个聪明的 @NonNullByDefault时,我们甚至不必每次都说这句话。

换句话说: null 注释是否“传播很远”可能取决于您使用的工具,以及您如何严格地处理工具发出的所有警告。使用 TYPE _ USE 空注释,您最终可以选择让工具警告您在程序中可能出现的 每个 NPE,因为空性已经成为类型系统的一个固有属性。

我认为这个最初的问题间接地指向了一个通用建议,即仍然需要运行时空指针检查,即使使用了@NonNull。请参阅以下连结:

Java8的新类型注释

在上述博客中,建议:

可选的类型注释不能替代运行时验证 在类型注释之前,描述事物的主要位置 类似于 nullability 或 range 都在 javadoc 中, 这种通信以编译时的方式进入字节码 您的代码仍然应该执行运行时验证。

我在我的项目中所做的就是在“常量条件和异常”代码检查中激活以下选项:
对于可能返回 null 并报告传递给非注释参数的可空值的方法,建议使用@Nullable 注释 Inspections

当激活时,所有未注释的参数都将被视为非 null,因此您还会在间接调用中看到一个警告:

clazz.indirectPathToA(null);

对于更强大的检查,检查框架可能是一个很好的选择(参见这个不错的 教程)。
注意: 我还没有使用它,而且 Jack 编译器可能存在问题: 请参阅这个 bugreport

如果你使用 Kotlin,它在编译器中支持这些 nullability 注释,并且会阻止你将 null 传递给需要非 null 参数的 java 方法。事件,虽然这个问题最初是针对 Java 的,但是我提到了 Kotlin 特性,因为它是特定的 针对这些 Java 注释,问题是 “有没有办法使这些注释更严格地执行和/或进一步传播?”,而这个特性是 使这些注释更严格地执行

使用 @NotNull注释的 Java 类

public class MyJavaClazz {
public void foo(@NotNull String myString) {
// will result in an NPE if myString is null
myString.hashCode();
}
}

Kotlin 类调用 Java 类并为用@NotNull 注释的参数传递 null

class MyKotlinClazz {
fun foo() {
MyJavaClazz().foo(null)
}
}

执行 @NotNull注释时发生 Kotlin 编译器错误。

Error:(5, 27) Kotlin: Null can not be a value of a non-null type String

见: http://kotlinlang.org/docs/reference/java-interop.html#nullability-annotations

由于 Java8的新特性 可以选择,您不应该再在自己的代码中使用@Nullable 或@Notnull。以下面的例子为例:

public void printValue(@Nullable myValue) {
if (myValue != null) {
System.out.print(myValue);
} else {
System.out.print("I dont have a value");
}

它可以改写为:

public void printValue(Optional<String> myValue) {
if (myValue.ifPresent) {
System.out.print(myValue.get());
} else {
System.out.print("I dont have a value");
}

使用可选的 强制您检查空值。在上面的代码中,您只能通过调用 get方法来访问该值。

另一个优点是 代码变得更具可读性。在添加了 Java9如果不是之后,这个函数甚至可以写成:

public void printValue(Optional<String> myValue) {
myValue.ifPresentOrElse(
v -> System.out.print(v),
() -> System.out.print("I dont have a value"),
)
}

@ NotNull from javax.validation.約束意味着方法永远不返回 null 值. . ;) 因此,这意味着在从该方法获取任何值时,不需要为检查空指针异常添加任何空检查。.校对: D