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

时间:2012-11-20 23:48:03

标签: java annotations nullpointerexception nullable code-standards

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

  • 这种注释的有效性在一个间接层之后完全下降,所以如果你只添加一些注释,它们就不会传播得很远。
  • 由于这些注释没有得到很好的执行,因此存在假设标记为@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        
    }
}

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

9 个答案:

答案 0 :(得分:53)

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

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

另一个好的提示是永远不会返回空值,而是使用Null Object Pattern代替。

答案 1 :(得分:22)

除了IDE给你的提示,你传递null预期不为空的地方,你还有其他优势:

  • 静态代码分析工具可以像IDE一样进行测试(例如FindBugs)
  • 您可以使用AOP检查此断言

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

答案 2 :(得分:11)

在兼容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()具有“遗留”签名,因为它没有指定任何空契约。工具可以很容易地报告这一点,因此他们会追逐所有需要传播空注释但尚未传播的小巷。

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

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

答案 3 :(得分:10)

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

Java 8's new Type Annotations

在上面的博客中,建议:

  

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

答案 4 :(得分:6)

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

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

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

以下代码段包含错误。该方法调用directPathToA()而不强制y为非空(即,它不保证被调用方法的前提条件)。一种可能性是向Nonnull添加indirectPathToA()注释(传播前置条件)。可能性二是检查yindirectPathToA()的无效性,并在directPathToA()为空时避免调用y

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

答案 5 :(得分:4)

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

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

clazz.indirectPathToA(null); 

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

答案 6 :(得分:3)

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

答案 7 :(得分:3)

如果您使用Kotlin,它会在其编译器中支持这些可空性注释,并且会阻止您将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)
    }
}  

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

答案 8 :(得分:-1)

自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"),
    )
}