为什么要使用Objects.requireNonNull()?


213

我注意到,Oracle JDK中使用了许多Java 8方法,如果给定的对象(参数)为,则会在Objects.requireNonNull()内部抛出NullPointerException该方法null

public static <T> T requireNonNull(T obj) {
    if (obj == null)
        throw new NullPointerException();
    return obj;
}

但是NullPointerException,如果null取消引用对象,则无论如何都会抛出该异常。那么,为什么要做这个额外的null检查并抛出 NullPointerException呢?

一个明显的答案(或好处)是它使代码更具可读性,我同意。我很想知道Objects.requireNonNull()在方法开始时使用的其他原因 。




1
通过这种参数检查方法,您可以在编写单元测试时作弊。Spring也有类似的实用程序(请参阅 docs.spring.io/spring/docs/current/javadoc-api/org/…)。如果您有一个“如果”并且想拥有较高的单元测试覆盖率,则必须覆盖两个分支:满足条件的时间和不满足条件的时间。如果您使用Objects.requireNonNull,则您的代码没有分支,因此单次通过单元测试将为您提供100%的覆盖率:-)
xorcus

Answers:


214

因为您可以这样做使事情变得明确。喜欢:

public class Foo {
  private final Bar bar;

  public Foo(Bar bar) {
    Objects.requireNonNull(bar, "bar must not be null");
    this.bar = bar;
  }

或更短:

  this.bar = Objects.requireNonNull(bar, "bar must not be null");

现在您知道了

  • 成功使用创建Foo对象时new()
  • 保证bar字段为非空。

比较一下:今天创建一个Foo对象,明天创建一个使用该字段并引发的方法。最有可能的是,明天您将不知道为什么昨天将该引用传递给构造函数后,该引用为空!

换句话说:通过显式使用此方法检查传入的引用,您可以控制引发异常的时间点。而且在大多数情况下,您想尽快失败

主要优点是:

  • 如前所述,控制行为
  • 调试更容易-因为您在创建对象的上下文中抛出错误。在某个时间点,您很有可能您的日志/跟踪记录会告诉您出了什么问题!
  • 如上所示:这个想法的真正力量与最终领域一起展现。因为现在您的课程中的任何其他代码都可以安全地假定它bar不是null-因此您不需要if (bar == null)在其他地方进行任何检查!

23
您可以通过编写代码使代码更紧凑this.bar = Objects.requireNonNull(bar, "bar must not be null");
Kirill Rakhman

3
@KirillRakhman教GhostCat一些我不知道的很棒的东西->在GhostCat支持的彩票中赢得彩票。
GhostCat

1
我认为这里的#1评论是的实际好处Objects.requireNonNull(bar, "bar must not be null");。谢谢你这个
卡乌(Kawu)

1
哪个是第一个,为什么“语言”人们应该跟随某个图书馆的作者?番石榴为什么不遵循Java lang行为?!
GhostCat

1
所述this.bar = Objects.requireNonNull(bar, "bar must not be null");如果两个或多个变量是在相同的方法设置在构造函数确定,但在其它方法中潜在的危险。示例:talkwards.com/2018/11/03/…–
Erk

99

快速失败

该代码应尽快崩溃。它不应完成一半的工作,然后取消引用null并崩溃,然后再完成一半的工作,导致系统处于无效状态。

这通常称为“早期失败”或“快速失败”


40

但是,如果取消引用空对象,则无论如何都会抛出NullPointerException。那么,为什么要做这个额外的null检查并抛出NullPointerException?

这意味着你发现问题,立即可靠

考虑:

  • 在您的代码已经产生一些副作用之后,该引用才可以在该方法的后面使用
  • 该引用可能完全无法在此方法中被取消引用
    • 可以将其传递给完全不同的代码(即,原因和错误在代码空间中相距很远)
    • 它可以在以后使用(即因果关系在时间上相差很远)
  • 它可用于地方,一个空引用有效的,但是有一个意想不到的效果

.NET通过将NullReferenceException(“您取消引用了空值”)与ArgumentNullException(“您不应该将null作为参数传递-而是用于参数)” 分开来实现了这一点。我希望Java做同样的事情,但是即使只是一个NullPointerException,如果在最早可以检测到错误的地方抛出错误,则修复代码仍然容易得多


12

使用requireNonNull()如在方法首先语句允许以确定现在/快异常的原因。
stacktrace清楚地表明,由于调用者不遵守需求/合同,因此在方法进入后立即引发了异常null对象传递给另一种方法确实确实可能一次引发一个异常,但是问题的原因可能更难以理解,因为该异常将在null对象上的特定调用中引发,而该调用可能会更远。


这是一个具体的真实的例子,说明了为什么我们通常不得不快速失败,更特别的是使用Object.requireNonNull()或任何方式对设计为not的参数进行no null检查null

假设一个Dictionary类由a LookupService和a 组成ListString表示包含在其中的单词。这些字段设计为not,null并且其中一个在Dictionary 构造函数中传递 。

现在假设在方法项中Dictionarynull检查的“错误”实现(这里是构造函数):

public class Dictionary {

    private final List<String> words;
    private final LookupService lookupService;

    public Dictionary(List<String> words) {
        this.words = this.words;
        this.lookupService = new LookupService(words);
    }

    public boolean isFirstElement(String userData) {
        return lookupService.isFirstElement(userData);
    }        
}


public class LookupService {

    List<String> words;

    public LookupService(List<String> words) {
        this.words = words;
    }

    public boolean isFirstElement(String userData) {
        return words.get(0).contains(userData);
    }
}

现在,让我们Dictionary使用参数的null引用来调用构造words函数:

Dictionary dictionary = new Dictionary(null); 

// exception thrown lately : only in the next statement
boolean isFirstElement = dictionary.isFirstElement("anyThing");

JVM在以下语句中抛出NPE:

return words.get(0).contains(userData); 
线程“主”中的异常java.lang.NullPointerException
    在LookupService.isFirstElement(LookupService.java:5)
    在Dictionary.isFirstElement(Dictionary.java:15)
    在Dictionary.main(Dictionary.java:22)

异常是在LookupService类中触发的,而其起源要早得多(Dictionary构造函数)。这使得整体问题分析变得不那么明显。
words null吗 是words.get(0) null吗 两者都?为什么一个,另一个或两者都是nullDictionary(构造函数?调用的方法?)中是否存在编码错误?是编码错误LookupService吗?(构造函数?被调用的方法?)?
最后,我们将不得不检查更多代码以查找错误根源,并且在更复杂的类中甚至可能使用调试器更轻松地了解发生了什么。
但是,为什么一件简单的事情(缺少null检查)会变成一个复杂的问题呢?
因为我们允许在较低组件的特定组件泄漏中识别出最初的错误/缺失。
想象一下,这LookupService不是本地服务,而是远程服务或带有很少调试信息的第三方库,或者想象在null被检测到之前,您没有2层而是4或5层对象调用?问题将变得更加复杂。

因此,青睐的方法是:

public Dictionary(List<String> words) {
    this.words = Objects.requireNonNull(words);
    this.lookupService = new LookupService(words);
}

这样,您就不会感到头疼:我们在收到异常后立即抛出异常:

// exception thrown early : in the constructor 
Dictionary dictionary = new Dictionary(null);

// we never arrive here
boolean isFirstElement = dictionary.isFirstElement("anyThing");
线程“主”中的异常java.lang.NullPointerException
    在java.util.Objects.requireNonNull(Objects.java:203)
    在com.Dictionary。(Dictionary.java:15)
    在com.Dictionary.main(Dictionary.java:24)

请注意,这里我用构造函数说明了这个问题,但是方法调用可能具有相同的非null检查约束。


哇!很好的解释。
乔纳萨斯·纳西门托

2

附带说明一下,Object#requireNotNull在某些jre类本身内部的java-9 之前,此快速失败之前实现的方式略有不同。假设情况:

 Consumer<String> consumer = System.out::println;

在java-8中,它编译为(仅相关部分)

getstatic Field java/lang/System.out
invokevirtual java/lang/Object.getClass

基本上,操作如下:yourReference.getClass-如果yourRefercence为,则该操作将失败null

在jdk-9中,事情发生了变化,其中的代码与

getstatic Field java/lang/System.out
invokestatic java/util/Objects.requireNonNull

或基本上 Objects.requireNotNull (yourReference)


1

基本用法是检查并抛出 NullPointerException立即。

满足相同要求的更好的替代方法(快捷方式)是lombok的@NonNull批注。


1
注释不能替代Objects方法,它们可以协同工作。您不能说@ NonNull x = mightBeNull,而是说@ NonNull x = Objects.requireNonNull(mightBeNull,“不可思议!”);
比尔K

@BillK对不起,我不得到ü
Supun Wijerathne

我只是说Nonnull注释与requireNonNull一起使用,这不是替代选择,但它们可以很好地协同工作。
比尔K,

实现快速失败的性质,这是另一种选择吗?是的,我同意这不是替代作业。
Supun Wijerathne,

我猜我会把requireNonNull()称为从@ Nullable到@ NonNull的“转换”。如果您不使用批注,则该方法并不是真的很有趣(因为它所做的只是像其保护的代码一样抛出NPE)-尽管它确实清楚地表明了您的意图。
比尔K,


0

我认为应该在复制构造函数和其他一些类似输入参数为对象的DI的情况下使用它,您应该检查参数是否为null。在这种情况下,您可以方便地使用此静态方法。


0

在实现Nullability检查的编译器扩展的上下文中(例如uber / NullAway),Objects.requireNonNull应谨慎使用某些情况,当您有一个可空字段,而您恰巧在代码中的某个点上知道该字段不为null时。

这样,有两种主要用法:

  • 验证方式

    • 已经被其他回应覆盖
    • 具有开销和潜在NPE的运行时检查
  • 可空性标记(从@Nullable更改为@Nonnull)

    • 运行时检查的最少使用,有利于编译时检查
    • 仅当注释正确时才有效(由编译器强制执行)

可空性标记的用法示例:

@Nullable
Foo getFoo(boolean getNull) { return getNull ? null : new Foo(); }

// Changes contract from Nullable to Nonnull without compiler error
@Nonnull Foo myFoo = Objects.requireNonNull(getFoo(false));
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.