我已经注意到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()。
因为这样做可以使事情显式化。如:
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)检查!
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
除了所有正确答案之外:
我们在反应流中使用它。通常产生的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将终止您的订阅。