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

Java 为什么要使用Objects.requireNonNull()?,java,java-8,nullpointerexception,Java,Java 8,Nullpointerexception,我注意到,Oracle JDK中的许多Java 8方法都使用对象。requireNonNull(),如果给定对象(参数)为null,它会在内部抛出NullPointerException public static <T> T requireNonNull(T obj) { if (obj == null) throw new NullPointerException(); return obj; } public static T requiren

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

public static <T> T requireNonNull(T obj) {
    if (obj == null)
        throw new NullPointerException();
    return obj;
}
public static T requirennull(T obj){
if(obj==null)
抛出新的NullPointerException();
返回obj;
}
但是,如果对
null
对象取消引用,则仍将抛出
NullPointerException
。那么,为什么要做这个额外的空检查并抛出
NullPointerException

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

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

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对象,明天调用一个使用该字段并抛出的方法。最有可能的是,您明天将不知道为什么昨天将引用传递给构造函数时该引用为null

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

主要优点是:

  • 如前所述,控制行为
  • 更容易调试-因为您在对象创建的上下文中抛出。在某个时间点,您的日志/跟踪可能会告诉您出了什么问题
  • 如上所示:这个想法的真正力量与最终领域一起展现。因为现在您的类中的任何其他代码都可以安全地假定
    bar
    不是空的-因此,如果(bar==null)在其他地方进行检查,您就不需要任何
    
    
快速故障 代码应该尽快崩溃。它不应该完成一半的工作,然后取消对null的引用,然后崩溃,只剩下一半的工作完成,从而导致系统处于无效状态

这通常被称为“提前失败”或

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

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

考虑:

  • 只有在代码执行了一些副作用之后,才能在方法的后面使用该引用
  • 在此方法中,引用可能根本不会被取消引用
    • 它可以传递给完全不同的代码(即,原因和错误在代码空间中相差很远)
    • 它可以在很久以后使用(即原因和错误在时间上相差很远)
  • 它可以在空引用有效但具有意外效果的地方使用
.NET通过将
NullReferenceException
(“您解除了对空值的引用”)与
ArgumentNullException
(“您不应该将空值作为参数传递,而这是针对此参数的)分开来实现这一点。我希望Java也能做同样的事情,但即使只使用一个
NullPointerException
,如果在可以检测到错误的最早点抛出错误,修复代码仍然容易得多。

使用
requirennull()
作为方法中的第一个语句,可以立即/快速识别异常的原因。
stacktrace清楚地表明,由于调用方不遵守要求/约定,异常在方法输入时立即抛出。
null
对象传递给另一个方法确实可能会在某个时间引发异常,但问题的原因可能更复杂,因为异常将在
null
对象上的特定调用中抛出,而该调用可能要远得多


下面是一个具体而真实的例子,说明了为什么我们必须支持快速失败,尤其是使用
Object.requirennull()
或任何方法对设计为非
null
的参数执行无空检查

假设一个
字典
类组成了一个
查找服务
和一个
列表
,其中
字符串
表示中包含的单词。这些字段被设计为非
,其中一个字段被传递到
字典
构造函数中

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

JVM在以下语句中抛出NPE:

return words.get(0).contains(userData); 
这样,就不会让人头疼了:我们一收到异常就会抛出异常:

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

// we never arrive here
boolean isFirstElement = dictionary.isFirstElement("anyThing");
线程“main”java.lang.NullPointerException中出现异常 位于java.util.Objects.requirennull(Objects.java:203) 在com.Dictionary.(Dictionary.java:15) 位于com.Dictionary.main(Dictionary.java:24)
请注意,这里我用构造函数说明了这个问题,但方法调用可能具有相同的非空检查约束。

作为旁注,在java-9之前,在一些jre类本身内部实现的
Object#requirentnull
之前,这会很快失败。假设这种情况:

 Consumer<String> consumer = System.out::println;
基本上是一个操作:
yourReference.getClass
——如果您的reference为
null
,该操作将失败

jdk-9中的情况已经发生了变化,其中编译的代码与

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

或者基本上是
Objects.requireNotNull(yourReference)
基本用法是立即检查并抛出
NullPointerException

一个更好的选择(捷径)来满足同样的需求
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)
 Consumer<String> consumer = System.out::println;
getstatic Field java/lang/System.out
invokevirtual java/lang/Object.getClass
getstatic Field java/lang/System.out
invokestatic java/util/Objects.requireNonNull
@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));
<T> T parseAndValidate(String payload) throws ParsingException { ... };
<T> T save(T t) throws DBAccessException { ... };
...
.map(this::parseAndValidate)
.map(this::save)
.retry(Retry.<T>allBut(ParsingException.class))
.onErrorContinue(
    throwable -> throwable.getClass().equals(ParsingException.class),
    parsingExceptionConsumer()
)
private int calculateStringLength(String input) {
        return Objects.
                requireNonNull(input, "input cannot be null").
                length();
    }