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

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

public static <T> T requireNonNull(T obj) {
if (obj == null)
throw new NullPointerException();
return obj;
}
但是如果null对象被解引用,无论如何都会抛出NullPointerException。那么,为什么要做这个额外的空检查并抛出 NullPointerException吗?< / p > 一个明显的答案(或好处)是它使代码更具可读性,我同意这一点。我很想知道使用的其他原因 Objects.requireNonNull()在方法的开头
174319 次浏览

因为你可以通过这样做使事物显式的。如:

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对象
  • 然后它的酒吧字段为保证非空。

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

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

主要优点是:

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

Fail-fast

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

这通常被称为“过早失败”。或“;fail-fast"。

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

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

考虑:

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

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

使用requireNonNull()作为方法中的第一个语句,允许立即/快速识别异常的原因 堆栈跟踪清楚地指出,一旦因为打电话的人不尊重要求/合同。方法的条目被抛出异常 将null对象传递给另一个方法五月确实会一次引发一个异常,但问题的原因可能更复杂,难以理解,因为异常将在null对象的特定调用中抛出,这可能要远得多。< / p >


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

假设有一个Dictionary类,由StringLookupServiceList组成。这些字段被设计为非null,其中一个字段被传递到Dictionary构造函数中。

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

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

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

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)
异常在LookupService类中触发,而它的起源更早(Dictionary构造函数)。这使得整体问题分析不那么明显 wordsnull吗?words.get(0) null是?都有? 为什么一个,另一个或可能两个都是null ? 是Dictionary(构造函数?调用方法?)?是LookupService中的编码错误吗?(构造函数?调用的方法?)?
最后,我们将不得不检查更多的代码来找到错误的根源,在一个更复杂的类中,甚至可能使用调试器来更容易地理解发生了什么 但是为什么一个简单的事情(缺少空检查)会变成一个复杂的问题 因为我们允许在特定组件上的初始错误/缺失在较低的组件上泄漏 想象一下,LookupService不是一个本地服务,而是一个远程服务或一个具有很少调试信息的第三方库,或者想象一下,在检测到null之前,你没有2层而是4或5层对象调用?这个问题分析起来会更加复杂。< / p >

所以帮忙的方法是:

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)

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

作为旁注,这个在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——如果你的reference是null,该操作将失败。

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

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

或者基本上是Objects.requireNotNull (yourReference)

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

满足相同需求的一个更好的选择(快捷方式)是lombok的@NonNull注释。

在稍后访问对象的null成员时抛出NPE(空指针异常)Objects.requireNonNull()从值被访问的地方回溯到它被初始化为null的地方,因此允许关注NPE的真实来源。

一个最简单的场景:

// Global String
String nullString = null;

调用空值:

public void useString() {
nullString.length();  // The source of exception, without using Objects.requireNonNull()
}

现在,如果我们在初始化时使用Objects.requireNonNull():

// Global String
String nullString = Objects.requireNonNull(null); // The source of exception which is indeed the actual source of NPE

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

在实现可空性检查的编译器扩展上下文(例如:超级/ NullAway)中,当你有一个可空字段,而你恰好知道在代码中的某一点不是空时,应该谨慎地使用Objects.requireNonNull

这样一来,主要有两种用法:

  • < p >验证

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

    • 最小限度地使用运行时检查,以支持编译时检查
    • 只有注释正确时才有效(由编译器强制执行)

Nullability标记用法示例:

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

除了所有正确答案之外:

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

举个例子:假设你有

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

其中parseAndValidaterequireNonNull中的NullPointerException包装为ParsingException

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

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

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

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

现在RetryExhaustException将终止你的订阅。

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

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

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

此代码返回传递给它的字符串长度作为参数——但是如果inputnull,则会抛出NPE。

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

另一个有用的东西是“异常消息”。是我自己手写的(在这种情况下是input cannot be null)。

还有一个额外的好处,静态分析工具通常知道标准库的@NonNull和@Nullable(这与显式检查null相同):

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