在方法中使用requireNonNull()
作为第一条语句可以立即/快速地确定异常的原因。堆栈跟踪明确指出,异常是因为调用者未遵守要求/合同而在方法入口处抛出的。将null
对象传递给另一个方法可能会在某个时刻引发异常,但问题的原因可能更加复杂,因为异常将在可能远得多的null
对象上的特定调用中抛出。
这里有一个具体的实例,展示了为什么我们必须通常采用快速失败,尤其是使用Object.requireNonNull()
或任何检查旨在不为null
的参数的方法。
假设有一个Dictionary
类,组成了一个LookupService
和一个包含单词的String
List
。这些字段旨在不为null
,并且其中之一在Dictionary
构造函数中被传递。
现在假设构造函数没有null
检查的“坏”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);
}
}
现在,让我们使用null
引用作为words
参数来调用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);
在 LookupService
类中触发了异常,但其根源要早得多(即 Dictionary
构造函数)。这使得整个问题分析变得不太明显。
words
是null
吗? words.get(0)
是null
吗?两者都是吗?
为什么一个或另一个,或者可能两者都是null
?
这是Dictionary
(构造函数?调用的方法?)中的编码错误吗?还是LookupService
中的编码错误?(构造函数?调用的方法?)
最后,我们将不得不检查更多代码以找到错误的来源,在更复杂的类中甚至可能需要使用调试器来更轻松地理解发生了什么。
但是为什么一个简单的事情(缺少空指针检查)会变成一个复杂的问题?
因为我们允许初始 bug/缺陷在特定组件上泄漏到较低级别的组件。
想象一下,如果LookupService
不是本地服务,而是远程服务或第三方库,具有很少的调试信息,或者想象一下在null
被检测之前有 2 层,而不是 4 或 5 层的对象调用?问题仍然更复杂。
因此,有利的方法是:
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");
在主线程中发生异常:"main" java.lang.NullPointerException
位于java.util.Objects.requireNonNull(Objects.java:203)
在com.Dictionary.(Dictionary.java:15)
在com.Dictionary.main(Dictionary.java:24)
请注意,这里我用构造函数说明了问题,但方法调用可能存在相同的非空检查约束。
requireNonNull
。如果下面的示例有意义,我必须承认IntelliJ建议的修复并不总是有效的。 - Christian Vincenzo Traina