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

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

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

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


当前回答

除了所有正确答案之外:

我们在反应流中使用它。通常产生的nullpointerexception根据它们在流中的出现情况被包装成其他异常。因此,我们以后可以很容易地决定如何处理错误。

举个例子:假设你有

<T> T parseAndValidate(String payload) throws ParsingException { ... };
<T> T save(T t) throws DBAccessException { ... };

其中parseAndValidate从ParsingException中的requireNonNull中wrapps NullPointerException。

现在你可以决定,例如什么时候重试或不重试:

...
.map(this::parseAndValidate)
.map(this::save)
.retry(Retry.<T>allBut(ParsingException.class))

如果不进行检查,将在save方法中发生NullPointerException,这将导致无休止的重试。甚至值得,想象一个长寿的订阅,补充道

.onErrorContinue(
    throwable -> throwable.getClass().equals(ParsingException.class),
    parsingExceptionConsumer()
)

现在retryextrestexception将终止您的订阅。

其他回答

Using requireNonNull() as first statements in a method allow to identify right now/fast the cause of the exception. The stacktrace indicates clearly that the exception was thrown as soon as the entry of the method because the caller didn't respect the requirements/contract. Passing a null object to another method may indeed provoke an exception at a time but the cause of the problem may be more complicated to understand as the exception will be thrown in a specific invocation on the null object that may be much further.


下面是一个具体而真实的例子,它展示了为什么我们必须在一般情况下支持快速失败,特别是使用Object.requireNonNull()或任何方式对设计为非空的参数执行无空检查。

假设有一个Dictionary类,它组成一个LookupService和一个String列表,表示包含在。这些字段被设计为非空,其中一个字段被传递给Dictionary构造函数。

现在假设Dictionary有一个“糟糕的”实现,在方法条目中没有空检查(这里是构造函数):

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构造函数,对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); 
Exception in thread "main" java.lang.NullPointerException
    at LookupService.isFirstElement(LookupService.java:5)
    at Dictionary.isFirstElement(Dictionary.java:15)
    at Dictionary.main(Dictionary.java:22)

The exception is triggered in the LookupService class while the origin of it is well earlier (the Dictionary constructor). It makes the overall issue analysis much less obvious. Is words null? Is words.get(0) null ? Both ? Why the one, the other or maybe both are null ? Is it a coding error in Dictionary (constructor? invoked method?) ? Is it a coding error in LookupService ? (constructor? invoked method?) ? Finally, we will have to inspect more code to find the error origin and in a more complex class maybe even use a debugger to understand more easily what it happened. But why a simple thing (a lack of null check) become a complex issue ? Because we allowed the initial bug/lack identifiable on a specific component leak on lower components. Imagine that LookupService was not a local service but a remote service or a third party library with few debugging information or imagine that you didn't have 2 layers but 4 or 5 layers of object invocations before that the null be detected ? The problem would be still more complex to analyse.

所以帮忙的方法是:

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");
Exception in thread "main" java.lang.NullPointerException
    at java.util.Objects.requireNonNull(Objects.java:203)
    at com.Dictionary.(Dictionary.java:15)
    at com.Dictionary.main(Dictionary.java:24)

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

除了所有正确答案之外:

我们在反应流中使用它。通常产生的nullpointerexception根据它们在流中的出现情况被包装成其他异常。因此,我们以后可以很容易地决定如何处理错误。

举个例子:假设你有

<T> T parseAndValidate(String payload) throws ParsingException { ... };
<T> T save(T t) throws DBAccessException { ... };

其中parseAndValidate从ParsingException中的requireNonNull中wrapps NullPointerException。

现在你可以决定,例如什么时候重试或不重试:

...
.map(this::parseAndValidate)
.map(this::save)
.retry(Retry.<T>allBut(ParsingException.class))

如果不进行检查,将在save方法中发生NullPointerException,这将导致无休止的重试。甚至值得,想象一个长寿的订阅,补充道

.onErrorContinue(
    throwable -> throwable.getClass().equals(ParsingException.class),
    parsingExceptionConsumer()
)

现在retryextrestexception将终止您的订阅。

因为这样做可以使事情显式化。如:

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");

现在你知道了:

当使用new()成功创建Foo对象时 那么它的bar字段保证是非空的。

与此相比:今天创建了一个Foo对象,明天调用一个使用该字段并抛出的方法。最有可能的是,明天您将不知道为什么该引用昨天传递给构造函数时为空!

换句话说:通过显式地使用此方法检查传入引用,您可以控制抛出异常的时间点。大多数时候,你都想尽快失败!

主要优点是:

如前所述,控制行为 更容易调试-因为在对象创建的上下文中抛出。在某个时间点,您的日志/跟踪有一定的机会告诉您哪里出了问题! 如上所示:这个想法的真正力量与最终场相结合。因为现在类中的任何其他代码都可以安全地假设bar不为空——因此您不需要在其他地方进行任何if (bar == null)检查!

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

快速失败

代码应该会尽快崩溃。它不应该做一半的工作,解引用null,然后才崩溃,只做了一半的工作,导致系统处于无效状态。

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