ChatGPT解决这个技术问题 Extra ChatGPT

如何更有效地使用@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        
    }
}

有没有办法让这些注释更严格地执行和/或进一步传播?

我喜欢 @Nullable@Nonnull 的想法,但如果它们值得,它非常“可能会引起辩论”
我认为转移到导致编译器错误或警告的世界的方法是在调用具有可为空变量的 @Nonnull 方法时要求强制转换为 @Nonnull。当然,在 Java 7 中无法使用注解进行强制转换,但 Java 8 将添加将注解应用于变量使用的能力,包括强制转换。所以这有可能在 Java 8 中实现。
@TheodoreMurdock,是的,在 Java 8 中,强制转换 (@NonNull Integer) y 在语法上是可能的,但不允许编译器根据注释发出任何特定的字节码。对于运行时断言,微小的辅助方法就足够了,如 bugs.eclipse.org/442103 中所讨论的(例如,directPathToA(assertNonNull(y))) - 但请注意,这只有助于快速失败。唯一安全的方法是执行实际的空值检查(希望在 else 分支中有一个替代实现)。
在这个问题中说出您在谈论哪个 @Nonnull@Nullable 会很有帮助,因为有多个类似的注解(请参阅 this question)。您是在谈论包 javax.annotation 中的注释吗?
@TJamesBoone 对于这个问题的上下文无关紧要,这是关于如何有效地使用它们中的任何一个。

P
Pedro Boechat

简短的回答:我猜这些注释仅对您的 IDE 有用,以警告您潜在的空指针错误。

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

另一个好技巧是永远不要返回空值,而是使用 Null Object Pattern


对于可能为空的返回值,我强烈建议使用 Optional 类型而不是普通的 null
可选的并不比“null”更好。 Optional#get() 抛出 NoSuchElementException,而使用 null 抛出 NullPointerException。两者都是没有有意义的描述的 RuntimeException。我更喜欢可为空的变量。
@30thh 为什么你会直接使用 Optional.get() 而不是 Optional.isPresent() 或 Optional.map 首先?
@GauravJ为什么要直接使用可空变量而不检查它是否先为空? ;-)
在这种情况下,Optional 和可为空的区别在于,Optional 可以更好地传达此值可以有意为空。当然,它不是一根魔杖,在运行时它可能会以与可空变量完全相同的方式失败。但是,在我看来,使用 Optional 程序员的 API 接收效果更好。
B
Brad Turek

除了当您将 null 传递给期望参数不为 null 的方法时,您的 IDE 会给您提示之外,还有其他优点:

静态代码分析工具可以像你的 IDE 一样测试(例如 FindBugs)

您可以使用面向方面的编程 (AOP) 来检查这些断言

这可以帮助您的代码更易于维护(因为您不需要 null 检查)并且不易出错。


我同情这里的 OP,因为即使你引用了这两个优点,在这两种情况下你都使用了“可以”这个词。这意味着不能保证这些检查会实际发生。现在,这种行为差异可能对您希望避免在生产模式下运行的性能敏感测试有用,我们有 assert。我发现 @Nullable@Nonnull 是有用的想法,但我希望它们背后有更多的力量,而不是我们假设人们可以用它们做什么,这仍然留下了可能性对他们什么都不做。
问题是从哪里开始。目前他的注释是可选的。有时我会喜欢它,如果它们不是,因为在某些情况下执行它们会有所帮助......
请问您在这里指的是什么AOP?
@Chris.Zou AOP 表示面向方面的编程,例如 AspectJ
G
GOTO 0

我认为这个原始问题间接指向了一个一般建议,即仍然需要运行时空指针检查,即使使用了@NonNull。请参考以下链接:

Java 8's new Type Annotations

在上面的博客中,建议:

可选类型注释不能替代运行时验证 在类型注释之前,描述可空性或范围等内容的主要位置是在 javadoc 中。使用类型注释,这种通信以编译时验证的方式进入字节码。您的代码仍应执行运行时验证。


可以理解,但是默认的 lint 检查警告说运行时空检查是不必要的,乍一看似乎不鼓励这个建议。
@swooby 如果我确定我的代码是正确的,我通常会忽略 lint 警告。这些警告不是错误。
S
Stephan Herrmann

在 Eclipse 中以合规性 1.8 编译原始示例并启用基于注释的 null 分析,我们收到以下警告:

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

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

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

换句话说:空注释是否“传播得很远”可能取决于您使用的工具,以及您对工具发出的所有警告的关注程度。使用 TYPE_USE null annotations,您最终可以选择让该工具警告您程序中每一个可能的 NPE,因为 nullness 已成为类型系统的固有属性。


T
TmTron

https://i.stack.imgur.com/bfG9h.png

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

clazz.indirectPathToA(null); 

对于更强大的检查,Checker Framework 可能是一个不错的选择(请参阅这个不错的 tutorial
注意:我还没有使用它,并且 Jack 编译器可能存在问题:请参阅 { 2}


这干净多了!
s
seh

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

我将 Nonnull 注释理解为文档。以下方法表示需要(作为前提条件)非空参数 x

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

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

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

@Nonnull 传播到 indirectPathToA(@Nonnull Integer y) 恕我直言,这是一种不好的做法:您需要在整个调用堆栈上维护传播(因此,如果您在 directPathToA() 中添加 null 签入,则需要替换 {1 } 由 @Nullable 在完整的调用堆栈中)。对于大型应用程序,这将是一项巨大的维护工作。
@Nonnull 注释只是强调参数的空验证在您身边(您必须保证您传递非空值)。这不是方法的责任。
当空值对此方法没有任何意义时,@Nonnull 也是明智之举
M
Mike Rylander

如果您使用 Kotlin,它会在其编译器中支持这些可空性注释,并将阻止您将空值传递给需要非空参数的 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


该问题针对 Java,根据其第一个标签,而不是 Kotlin。
@seh 请参阅更新,了解为什么此答案与此问题相关。
很公平。这是 Kotlin 的一个很好的特性。我只是认为它不会满足那些来这里学习 Java 的人。
但是即使没有在参数中添加 @NotNull,访问 myString.hashCode() 仍然会抛出 NPE。那么添加它的更具体的内容是什么?
@kAmol 这里的区别在于,使用 Kotlin 时,您将收到编译时错误而不是运行时错误。注解是通知你开发者需要确保没有传入 null。这不会阻止在运行时传入 null,但会阻止你编写使用 null 调用此方法的代码(或使用可以返回 null 的函数)。
I
Ionuț G. Stan

在 Java 中,我会使用 Guava's Optional type。作为一种实际类型,您可以获得编译器对其使用的保证。绕过它并获得 NullPointerException 很容易,但至少该方法的签名清楚地传达了它期望作为参数的内容或可能返回的内容。


你必须小心这个。 Optional 应该只在一个值是真正可选的情况下使用,并且没有它被用作进一步逻辑的决策门。我已经看到这被滥用了,用 Optionals 和 null 检查替换为存在的检查,这没有抓住重点。
如果您的目标是 JDK 8 或更高版本,则更喜欢使用 java.util.Optional 而不是 Guava 的类。 See Guava's notes/comparison 了解有关差异的详细信息。
“用存在检查代替空检查,这没有抓住重点”你能详细说明一下,重点是什么?在我看来,这不是 Optionals 的唯一原因,但它肯定是迄今为止最大和最好的一个。
C
César Alforde

由于 Java 8 的新特性Optional,您不应再在自己的代码中使用 @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 方法来访问该值。

另一个优点是代码更具可读性。随着 Java 9 ifPresentOrElse 的加入,该函数甚至可以写成:

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

即使使用 Optional,仍有许多库和框架使用这些注释,因此使用更新版本来更新/替换所有依赖项以使用 Optionals 是不可行的。但是,Optional 在您在自己的代码中使用 null 的情况下会有所帮助。