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


140

我可以看到@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        
    }
}

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


1
我喜欢@Nullableor 的想法@Nonnull,但是如果值得,那么“很可能引发辩论”
Maarten Bodewes 2012年

我认为,进入一个会导致编译器错误或警告的世界的方法是,@Nonnull在调用@Nonnull带有可为空的变量的方法时需要强制转换为。当然,在Java 7中无法使用注释进行强制转换,但是Java 8将添加将注释应用于变量(包括强制转换)的功能。所以,这可能会成为可能在Java中8.实施
西奥多·默多克

1
@TheodoreMurdock,是的,在Java 8 (@NonNull Integer) y中语法上可以进行强制转换,但是不允许编译器基于注释发出任何特定的字节码。对于运行时断言,如bugs.eclipse.org/442103(例如directPathToA(assertNonNull(y)))中所讨论的那样,微小的辅助方法就足够了-但是请注意,这只会帮助快速失败。唯一安全的方法是执行实际的空检查(希望在else分支中加上替代实现)。
Stephan Herrmann

1
这将是有益的在这个问题上说哪个@Nonnull@Nullable你所谈论的,因为有多个类似annoations(见这个问题)。您是在谈论包装中的注释javax.annotation吗?
James Dunn

1
@TJamesBoone对于此问题的上下文并不重要,这与如何有效使用它们有关。
Mike Rylander

Answers:


66

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

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

另一个好技巧是永远不要返回空值,而应使用空对象模式


10
对于可能为空的返回值,我强烈建议使用该Optional类型而不是普通null
Patrick

7
可选ist不比“ null”好。Optional#get()抛出NoSuchElementException,而使用null则抛出NullPointerException。两者都是RuntimeException的,没有有意义的描述。我更喜欢空变量。
18th

4
@ 30thh为什么要直接使用Optional.get()而不先使用Optional.isPresent()或Optional.map?
GauravJ

7
@GauravJ为什么要直接使用可为空的变量,而不先检查它是否为null?;-)
18th

5
Optional在这种情况下,和nullable 之间的区别在于,可以Optional更好地传达该值可以有意为空的信息。当然,它不是魔术棒,在运行时它可能以与可为空的变量完全相同的方式失败。但是,Optional我认为程序员最好接受API 。
user1053510

31

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

  • 静态代码分析工具可以测试与您的IDE相同的工具(例如FindBugs)
  • 您可以使用AOP检查这些断言

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


9
我在这里表示同情OP,因为尽管您引用了这两个优点,但在两种情况下都使用了“可以”一词。这意味着不能保证这些检查将实际发生。现在,这种行为上的差异对于希望避免在生产模式下运行的性能敏感测试很有用,因为我们已经为此进行了测试assert。我发现@Nullable并且@Nonnull是有用的想法,但我希望在它们后面施加更多的力量,而不是让我们假设一个人可以用它们做什么,这仍然使人们有可能对它们不做任何事情。
seh 2012年

2
问题是从哪里开始。目前,他的注释是可选的。有时,如果他们不这样做,我会很喜欢,因为在某些情况下执行它们会有所帮助...
Uwe Plonus 2012年

请问您在这里指的是什么AOP?
Chris.Zou 2015年

@ Chris.Zou AOP表示面向方面的编程,例如AspectJ
Uwe Plonus

13

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

Java 8的新类型注释

在以上博客中,建议:

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


1
可以理解,但是默认的lint检查警告运行时null检查是不必要的,乍一看似乎不建议这样做。
swooby

1
@swooby如果我确定我的代码正确,通常我会忽略棉绒警告。这些警告不是错误。
jonathanzh

12

在Compliance 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空注解你终于有让工具警告你选择每一个在你的程序可能NPE,因为NULL的含量已成为类型系统的intrisic财产。


8

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

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

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

以下代码段包含一个错误。该方法在directPathToA()不强制y为非null的情况下进行调用(也就是说,它不保证所调用方法的先决条件)。一种可能性是也要添加Nonnull注释indirectPathToA()(传播先决条件)。可能性二是检查yin 的无效性,indirectPathToA()并避免调用directPathToA()when y为null。

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

1
传播@Nonnull 具有indirectPathToA(@Nonnull Integer y)IMHO是一个不好的做法:您将需要维护完整调用堆栈上的传播(因此,如果添加null签入directPathToA(),则需要在完整调用堆栈中替换@Nonnull@Nullable)。对于大型应用程序,这将是一项巨大的维护工作。
朱利安·克朗格

@Nonnull批注仅强调参数的null验证就在您的一边(您必须确保传递非null值)。这不是该方法的责任。
亚历山大·德罗比雪夫斯基

当空值对此方法没有任何意义时,@ Nonnull也是明智的选择
Alexander Drobyshevsky

5

我在项目中所做的是在“恒定条件与异常”代码检查中激活以下选项:
建议@Nullable批注用于可能返回空值并报告传递给非批注参数的可空值的方法 视察

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

clazz.indirectPathToA(null); 

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


4

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


16
您必须对此小心。仅当值是真正的可选值,而没有值用作进一步逻辑的决策门时,才应使用可选值。我已经看到这种滥用,即用Optionals全面替换Objects并将空检查替换为错过了要点的状态检查。
Christopher Perry 2014年

如果您的目标是JDK 8或更高版本,则最好使用java.util.Optional而不是Guava的类。有关差异的详细信息,请参见番石榴的注释/比较
AndrewF

1
你能否详细说明的话,就点什么“null检查以检查是否存在其忽略了一点改为” ?在我看来,这不是选择Optional 的唯一原因,但肯定是迄今为止最大和最好的选择。
scubbo

4

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


3
这个问题针对的是Java,不是Java的,而是它的第一个标签。
SEH

1
@seh有关此答案为何与此问题相关的信息,请参阅更新。
Mike Rylander

2
很公平。这是Kotlin的一个不错的功能。我只是认为这不会满足那些来这里学习Java的人们的需求。
seh

但是myString.hashCode()即使@NotNull未在参数中添加访问,仍然会抛出NPE 。那么,添加它的更具体之处是什么?
kAmol '18 -10-17

@kAmol这里的区别是,使用Kotlin时,您将得到编译时错误而不是运行时错误。批注是为了通知您开发人员需要确保不传入null。这不会阻止在运行时传入null,但是会阻止您编写使用null调用此方法的代码(或具有可以返回null的函数)。
Mike Rylander

-4

由于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的情况下可以提供帮助。
Mike Rylander
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.