我已经注意到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()。


当前回答

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

其他回答

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

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。那么,为什么要做这个额外的空检查和抛出NullPointerException?

这意味着您可以立即可靠地检测到问题。

考虑:

在代码已经执行了一些副作用之后,该引用才可能在方法的后面使用 在此方法中,引用可能根本不会被解除引用 它可以传递给完全不同的代码(即原因和错误在代码空间中相隔很远) 它可以在很久以后使用(即原因和错误在时间上相差很远) 它可以用在空引用是有效的地方,但会产生意想不到的效果

. net通过分离NullReferenceException(“你解引用了一个空值”)和ArgumentNullException(“你不应该把null作为参数传入-它是为这个参数而传入的)来更好地解决这个问题。我希望Java也能做同样的事情,但即使只有NullPointerException,如果在最早可以检测到错误的时候抛出错误,修复代码仍然容易得多。

作为旁注,在object# requireNotNull之前,在java-9之前的一些jre类中实现的这个失败速度略有不同。假设如下情况:

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

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

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

基本上是一个操作:yourReference。getClass -如果你的引用为空,它将失败。

在jdk-9中发生了变化,相同的代码编译为

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

或者基本上是对象。requireNotNull(一份)

除了其他答案-对我来说,使用requireNonNull可以使代码更方便,(有时容易阅读)

例如,让我们检查下面的代码,

private int calculateStringLength(String input) {
        return Objects.
                requireNonNull(input, "input cannot be null").
                length();
    }

这段代码返回传递给它的字符串长度作为参数——但是如果输入为空,它将抛出一个NPE。

正如您所看到的,使用requireNonNull -没有理由再手动执行空检查了。

另一个有用的东西是“异常消息”是我自己手写的(在这种情况下输入不能为空)。