提问人:user4686046 提问时间:8/11/2017 最后编辑:Salehuser4686046 更新时间:7/12/2023 访问量:213668
为什么要使用 Objects.requireNonNull()?
Why should one use Objects.requireNonNull()?
问:
我注意到 Oracle JDK 中的许多 Java 8 方法都使用 ,如果给定的对象(参数)是 ,则在内部抛出 。Objects.requireNonNull()
NullPointerException
null
public static <T> T requireNonNull(T obj) {
if (obj == null)
throw new NullPointerException();
return obj;
}
但是,如果对象被取消引用,则无论如何都会被抛出。那么,为什么要做这个额外的 null 检查和抛出呢?NullPointerException
null
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()
- 则保证其 bar 字段为非 null。
相比之下:你今天创建一个 Foo 对象,明天你调用一个使用该字段并抛出的方法。最有可能的是,你明天不会知道为什么昨天当它传递给构造函数时该引用为 null!
换言之:通过显式使用此方法检查传入的引用,可以控制引发异常的时间点。大多数时候,你希望尽可能快地失败!
主要优点是:
- 如前所述,受控行为
- 更轻松的调试 - 因为您在对象创建上下文中抛出。在某个时间点,您的日志/跟踪有一定的机会告诉您出了什么问题!
- 如上图所示:这个想法的真正力量与最终领域一起展开。因为现在你的类中的任何其他代码都可以安全地假设它不是 null - 因此你不需要在其他地方进行任何检查!
bar
if (bar == null)
评论
this.bar = Objects.requireNonNull(bar, "bar must not be null");
Objects.requireNonNull(bar, "bar must not be null");
this.bar = Objects.requireNonNull(bar, "bar must not be null");
Objects.requireNonNull
this.bar = requireNonNull(...)
快速故障
代码应尽快崩溃。它不应该完成一半的工作,取消引用 null 然后才崩溃,留下一半的工作完成,导致系统处于无效状态。
这通常称为“早期失败”或“快速失败”。
但是,如果取消引用 null 对象,则无论如何都会引发 NullPointerException。那么,为什么要进行这个额外的 null 检查并抛出 NullPointerException?
这意味着您可以立即可靠地检测到问题。
考虑:
- 在代码已经执行了一些副作用之后,在方法的后面才可能使用该引用
- 在此方法中,引用可能根本无法取消引用
- 它可以传递给完全不同的代码(即原因和错误在代码空间中相距甚远)
- 它可以在很久以后使用(即原因和错误在时间上相距甚远)
- 它可能用于 null 引用有效但具有意外效果的任何地方
.NET 通过将 (“你取消了引用 null 值”) 与 (“你不应该将 null 作为参数传入 - 它是针对此参数的 ) 分开来改进这一点。我希望 Java 做同样的事情,但即使只有一个 ,如果错误在可以检测到的最早点抛出,修复代码仍然容易得多。NullReferenceException
ArgumentNullException
NullPointerException
在方法中使用 as first 语句可以立即/快速识别异常原因。
堆栈跟踪清楚地表明,由于调用方不遵守要求/协定,因此在方法输入后立即引发异常。将对象传递给另一个方法确实可能一次引发异常,但问题的原因可能更难以理解,因为异常将在对对象的特定调用中引发,这可能会更远。requireNonNull()
null
null
这是一个具体而真实的例子,它说明了为什么我们必须支持快速失败,更具体地说,使用或任何方式对设计为 not 的参数执行无空检查。Object.requireNonNull()
null
假设一个类,它由 a 和 a 组成,表示 中包含的单词。这些字段被设计为不是,其中一个字段在构造函数中传递。Dictionary
LookupService
List
String
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);
}
}
现在,让我们使用参数的引用调用构造函数:Dictionary
null
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)
异常是在类中触发的,而它的起源要早得多(构造函数)。它使整体问题分析变得不那么明显。
是?是?双?
为什么一个,另一个或两个?
是(构造函数? 调用的方法?)中的编码错误吗?是编码错误吗?(构造函数? 调用的方法??
最后,我们将不得不检查更多的代码来找到错误的根源,在更复杂的类中,甚至可以使用调试器来更容易地理解它发生了什么。
但是,为什么一件简单的事情(缺少空检查)变成了一个复杂的问题呢?
因为我们允许在特定组件上识别初始错误/缺失,从而在较低组件上泄漏。
想象一下,这不是一个本地服务,而是一个远程服务或调试信息很少的第三方库,或者想象一下,在检测到之前,你没有 2 层,而是 4 或 5 层对象调用?这个问题的分析将更加复杂。LookupService
Dictionary
words
null
words.get(0) null
null
Dictionary
LookupService
LookupService
null
所以偏爱的方式是:
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)
请注意,这里我用构造函数说明了这个问题,但方法调用可能具有相同的非 null 检查约束。
评论
顺便说一句,在java-9之前,在一些jre类本身中实现的这种快速失败略有不同。假设情况:Object#requireNotNull
Consumer<String> consumer = System.out::println;
在 java-8 中,这编译为(仅相关部分)
getstatic Field java/lang/System.out
invokevirtual java/lang/Object.getClass
基本上是一个操作: - 如果 yourRefercence 是 ,这将失败。yourReference.getClass
null
在 jdk-9 中,情况发生了变化,其中相同的代码编译为
getstatic Field java/lang/System.out
invokestatic java/util/Objects.requireNonNull
或者基本上Objects.requireNotNull (yourReference)
基本用法是立即检查和抛出。NullPointerException
满足相同要求的一个更好的选择(快捷方式)是 lombok @NonNull注释。
评论
NPE(空指针异常)在稍后访问对象的成员时引发。 从访问值的位置回溯到初始化为 null 的位置,从而允许专注于 NPE 的真正来源。null
Objects.requireNonNull()
最简单的场景:
// Global String
String nullString = null;
调用 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,您应该检查参数是否为 null。 在这种情况下,您可以方便地使用此静态方法。
在实现可为空性检查的编译器扩展的上下文中(例如:uber/NullAway),当您碰巧知道在代码中的某个点上有一个可为 null 的字段时,应谨慎使用。Objects.requireNonNull
这样,主要有两种用法:
验证
- 此处的其他回复已经涵盖
- 具有开销和潜在 NPE 的运行时检查
可空性标记(从 @Nullable 更改为 @Nonnull)
- 最少地使用运行时检查,支持编译时检查
- 仅当注释正确时才有效(由编译器强制执行)
可空性标记的用法示例:
@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));
除了所有正确答案:
我们在反应式流中使用它。通常,生成的 s 会根据它们在流中的出现情况包装到其他 Exception 中。因此,我们以后可以轻松决定如何处理错误。NullpointerException
举个例子:想象一下你有
<T> T parseAndValidate(String payload) throws ParsingException { ... };
<T> T save(T t) throws DBAccessException { ... };
其中 wrapps 在 .parseAndValidate
NullPointerException
requireNonNull
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();
}
此代码返回作为参数传递给它的字符串的长度 - 但是,如果 是 ,它将抛出 NPE。input
null
如您所见,使用 - 没有理由再手动执行 null 检查。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;
}
评论
Objects.requireNonNull