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

use*_*046 158 java nullpointerexception java-8

为什么要使用Objects.requireNonNull()

我注意到Oracle JDK中使用了许多Java 8方法,如果给定的对象(参数)是NullPointerException内部抛出nullNullPointerException.

public static <T> T requireNonNull(T obj) {
    if (obj == null)
        throw new NullPointerException();
    return obj;
}
Run Code Online (Sandbox Code Playgroud)

但是null如果NullPointerException对象被解除引用,则无论如何都会抛出.那么,为什么要进行额外的空检查和抛出 Objects.requireNonNull()

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

Gho*_*ica 159

因为你可以通过这样做来明确.喜欢:

public class Foo {
  private final Bar bar;

  public Foo(Bar bar) {
    Objects.requireNonNull(bar, "bar must not be null");
    this.bar = bar;
  }
Run Code Online (Sandbox Code Playgroud)

或更短:

  this.bar = Objects.requireNonNull(bar, "bar must not be null");
Run Code Online (Sandbox Code Playgroud)

现在你知道了:

  • 使用时成功创建Foo对象时new()
  • 那么它的bar字段保证是非null的.

比较一下:你今天创建一个Foo对象,明天你调用一个使用该字段并抛出的方法.最有可能的,你不会知道明天为什么引用是空昨天当它得到传递给构造!

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

主要优点是:

  • 如上所述,受控行为
  • 更简单的调试 - 因为你在对象创建的上下文中呕吐.在某个时间点,你有一定几率你的日志/痕迹告诉你出了什么问题!
  • 并且如上所示:这个想法的真正力量与最终领域一起展开.因为现在你的类中的任何其他代码都可以安全地假设它bar不是null - 因此你不需要if (bar == null)在其他地方进行任何检查!

  • 您可以通过编写`this.bar = Objects.requireNonNull(bar,"bar不能为空")来使代码更紧凑; (18认同)
  • 由于 `Objects.requireNonNull` 是一个静态方法,因此可以进行静态导入以使代码更短且更易于阅读:`this.bar = requireNonNull(...)`。 (4认同)
  • @KirillRakhman教GhostCat一些我不知道的很酷的东西-&gt;在GhostCat支持的彩票中赢得彩票。 (3认同)
  • 我认为这里的注释 #1 是 `Objects.requireNonNull(bar, "bar must not be null");` 的实际好处。感谢这个。 (2认同)
  • `this.bar = Objects.requireNonNull(bar, "bar must not be null");` 在构造函数中是可以的,但如果在同一个方法中设置两个或多个变量,则在其他方法中可能存在危险。示例:https://talkwards.com/2018/11/03/one-thing-to-avoid-when-using-objects-requirenonnull-in-java-8/ (2认同)

luk*_*302 79

快速失败

代码应该尽快崩溃.它不应该执行一半的工作,然后取消引用null并崩溃,只留下一半的工作完成导致系统处于无效状态.

这通常称为"早期失败"或"失败快速".


Jon*_*eet 34

但是如果取消引用null对象,则无论如何都会抛出NullPointerException.那么,为什么要进行额外的空检查并抛出NullPointerException呢?

这意味着你发现问题,立即可靠.

考虑:

  • 在您的代码已经执行了一些副作用之后,该方法的后期可能不会使用该引用
  • 根本不能在该方法中取消引用该引用
    • 它可以传递给完全不同的代码(即代码空间中的原因和错误相距甚远)
    • 它可以在以后使用很久(即原因和错误在时间上相隔很远)
  • 可以的地方,空引用中使用有效的,但是有一个意想不到的效果

.NET通过将NullReferenceException("你取消引用一个空值")与ArgumentNullException("你不应该将null作为参数传递 - 并且它是针对这个参数)使得这更好.我希望Java做同样的事情,但即使只是a NullPointerException,如果在可以检测到错误的最早点抛出错误,修复代码仍然容易得多.

  • 我从C#land获得了有趣的一点;-) (4认同)

Mir*_*han 17

null当您访问稍后的对象的成员时,会引发NPE(空指针异常) 。Objects.requireNonNull()从访问值的位置回溯到其初始化为 null 的位置,从而可以专注于 NPE 的真正来源。

一个最简单的场景:

// Global String
String nullString = null;
Run Code Online (Sandbox Code Playgroud)

调用空值:

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

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

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

  • 我将重新表述一下:它立即指示空值**出现**的位置和时间,而不是首次访问该值的时间(使用 NPE)。 (2认同)

dav*_*xxx 8

使用requireNonNull()如在方法首先语句允许以确定现在/快异常的原因。
stacktrace清楚地表明,由于调用者不遵守需求/合同,因此在方法进入后立即引发了异常null对象传递给另一种方法确实确实可能一次引发一个异常,但是问题的原因可能更难以理解,因为该异常将在null对象上的特定调用中引发,而该调用可能会更远。


这是一个具体而真实的例子,说明了为什么我们通常不得不快速失败,更特别的是使用Object.requireNonNull()或任何方法对设计为not的参数进行no null检查null

假设一个Dictionary类由a LookupService和a 组成ListString表示包含在其中的单词。这些字段设计为not,null并且其中一个在Dictionary 构造函数中传递 。

现在假设在方法项中Dictionarynull检查的“错误”实现(这里是构造函数):

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);
    }
}
Run Code Online (Sandbox Code Playgroud)

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

Dictionary dictionary = new Dictionary(null); 

// exception thrown lately : only in the next statement
boolean isFirstElement = dictionary.isFirstElement("anyThing");
Run Code Online (Sandbox Code Playgroud)

JVM在以下语句中抛出NPE:

return words.get(0).contains(userData); 
Run Code Online (Sandbox Code Playgroud)
线程“主”中的异常java.lang.NullPointerException
    在LookupService.isFirstElement(LookupService.java:5)
    在Dictionary.isFirstElement(Dictionary.java:15)
    在Dictionary.main(Dictionary.java:22)

异常是在LookupService类中触发的,而其起源要早得多(Dictionary构造函数)。这使得整体问题分析变得不那么明显。
words null吗 是words.get(0) null吗 两者都?为什么一个,另一个或两者都是nullDictionary(构造函数?调用的方法?)中是否存在编码错误?是编码错误LookupService吗?(构造函数?被调用的方法?)?
最后,我们将不得不检查更多代码以查找错误根源,并且在更复杂的类中甚至可能使用调试器更轻松地了解发生了什么。
但是,为什么一件简单的事情(缺少null检查)会变成一个复杂的问题呢?
因为我们允许在较低组件的特定组件泄漏中识别出最初的错误/缺失。
想象一下,这LookupService不是本地服务,而是远程服务或带有很少调试信息的第三方库,或者想象在null被检测到之前,您没有2层而是4或5层对象调用?问题将变得更加复杂。

因此,青睐的方法是:

public Dictionary(List<String> words) {
    this.words = Objects.requireNonNull(words);
    this.lookupService = new LookupService(words);
}
Run Code Online (Sandbox Code Playgroud)

这样,您就不会感到头疼:我们在收到异常后立即抛出异常:

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

// we never arrive here
boolean isFirstElement = dictionary.isFirstElement("anyThing");
Run Code Online (Sandbox Code Playgroud)
线程“主”中的异常java.lang.NullPointerException
    在java.util.Objects.requireNonNull(Objects.java:203)
    在com.Dictionary。(Dictionary.java:15)
    在com.Dictionary.main(Dictionary.java:24)

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

  • 哇!很好的解释。 (2认同)
  • 我同意@JonathasNascimento,非常好的解释和示例的使用。 (2认同)