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

Posted

技术标签:

【中文标题】如何更有效地使用@Nullable 和@Nonnull 注解?【英文标题】:How to use @Nullable and @Nonnull annotations more effectively? 【发布时间】:2012-11-09 04:03:58 【问题描述】:

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

这些注释的有效性在一级间接后完全下降,所以如果你只添加一些,它们不会传播很远。 由于这些注释没有得到很好的执行,因此存在假设标有@Nonnull 的值不为空而导致不执行空检查的危险。

下面的代码使标有@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 对于这个问题的上下文并不重要,这是关于如何有效地使用它们。 【参考方案1】:

简短回答:我猜这些注释仅对您的 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 接收效果更好。【参考方案2】:

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

静态代码分析工具可以像您的 IDE 一样进行测试(例如 FindBugs) 您可以使用Aspect-oriented programming (AOP) 来检查这些断言

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

【讨论】:

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

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

Java 8's new Type Annotations

在上面的博客中,建议:

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

【讨论】:

明白,但默认的 lint 检查警告说运行时空检查是不必要的,乍一看似乎不鼓励这个建议。 @swooby 如果我确定我的代码是正确的,我通常会忽略 lint 警告。这些警告不是错误。【参考方案4】:

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

    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 已成为类型系统的固有属性。

【讨论】:

【参考方案5】:

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

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

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

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

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

【讨论】:

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

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

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

clazz.indirectPathToA(null); 

对于更强大的检查,Checker Framework 可能是一个不错的选择(请参阅这个不错的 tutorial。注意:我还没有使用过,Jack 编译器可能存在问题:见this bugreport

【讨论】:

【参考方案7】:

如果您使用 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)
    
  

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

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 时,您会得到 compile 时间错误,而不是 runtime 错误。注解是通知你开发者需要确保没有传入 null。这不会阻止在运行时传入 null,但会阻止你编写使用 null 调用此方法的代码(或带有可以返回 null 的函数)。【参考方案8】:

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

【讨论】:

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

由于 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 的情况下提供帮助。

以上是关于如何更有效地使用@Nullable 和@Nonnull 注解?的主要内容,如果未能解决你的问题,请参考以下文章

如何使用 Urllib2 更有效地抓取?

在 Spring 框架中如何更有效地使用 JDBC?

如何在mysql中使用laravel迁移将数据库列'null'更改为'nullable'?

如何使用 LESS 更有效地使用媒体查询?

OC + RAC RACSignal 基本使用

如何更有效地使用 RunApp 功能来更改页面