Why should one use Objects.requireNonNull()?

2020-01-30 02:29发布

I have noted that many Java 8 methods in Oracle JDK use Objects.requireNonNull(), which internally throws NullPointerException if the given object (argument) is null.

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

But NullPointerException will be thrown anyway if a null object is dereferenced. So, why should one do this extra null check and throw NullPointerException?

One obvious answer (or benefit) is that it makes code more readable and I agree. I'm keen to know any other reasons for using Objects.requireNonNull() in the beginning of the method.

7条回答
该账号已被封号
2楼-- · 2020-01-30 03:00

Null pointer exception is thrown when you access a member of an object which is null at a later point. Objects.requireNonNull() immediately checks the value and throws exception instantly without moving forward.

查看更多
欢心
3楼-- · 2020-01-30 03:06

Using requireNonNull() as first statements in a method allow to identify right now/fast the cause of the exception.
The stacktrace indicates clearly that the exception was thrown as soon as the entry of the method because the caller didn't respect the requirements/contract. Passing a null object to another method may indeed provoke an exception at a time but the cause of the problem may be more complicated to understand as the exception will be thrown in a specific invocation on the null object that may be much further.


Here is a concrete and real example that shows why we have to favor fail fast in general and more particularly using Object.requireNonNull() or any way to perform a no null check on parameters designed to be not null.

Suppose a Dictionary class that composes a LookupService and a List of String representing words contained in. These fields are designed to be not null and one of these is passed in the Dictionary constructor.

Now suppose a "bad" implementation of Dictionary without null check in the method entry (here that is the constructor):

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

Now, let's invoke the Dictionary constructor with a null reference for the words parameter :

Dictionary dictionary = new Dictionary(null); 

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

The JVM throws the NPE at this statement :

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)

The exception is triggered in the LookupService class while the origin of it is well earlier (the Dictionary constructor). It makes the overall issue analysis much less obvious.
Is words null? Is words.get(0) null ? Both ? Why the one, the other or maybe both are null ? Is it a coding error in Dictionary (constructor? invoked method?) ? Is it a coding error in LookupService ? (constructor? invoked method?) ?
Finally, we will have to inspect more code to find the error origin and in a more complex class maybe even use a debugger to understand more easily what it happened.
But why a simple thing (a lack of null check) become a complex issue ?
Because we allowed the initial bug/lack identifiable on a specific component leak on lower components.
Imagine that LookupService was not a local service but a remote service or a third party library with few debugging information or imagine that you didn't have 2 layers but 4 or 5 layers of object invocations before that the null be detected ? The problem would be still more complex to analyse.

So the way to favor is :

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

In this way, no headache : we get the exception thrown as soon as this is received :

// 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)

Note that here I illustrated the issue with a constructor but a method invocation could have the same non null check constraint.

查看更多
混吃等死
4楼-- · 2020-01-30 03:11

As a side note, this fail fast before Object#requireNotNull was implemented slightly different before java-9 inside some of the jre classes themselves. Suppose the case :

 Consumer<String> consumer = System.out::println;

In java-8 this compiles as (only the relevant parts)

getstatic Field java/lang/System.out
invokevirtual java/lang/Object.getClass

Basically an operation as : yourReference.getClass - which would fail if yourRefercence is null.

Things have changed in jdk-9 where the same code compiles as

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

Or basically Objects.requireNotNull (yourReference)

查看更多
Evening l夕情丶
5楼-- · 2020-01-30 03:12

But NullPointerException will be thrown anyway if a null object is dereferenced. So, why should one do this extra null check and throw NullPointerException?

It means you detect the problem immediately and reliably.

Consider:

  • The reference may not be used until later in the method, after your code has already performed some side-effects
  • The reference may not be dereferenced in this method at all
    • It could be passed to completely different code (i.e. cause and error are far apart in code space)
    • It could be used much later (i.e. cause and error are far apart in time)
  • It may be used somewhere that a null reference is valid, but has an unintended effect

.NET makes this better by separating NullReferenceException ("you dereferenced a null value") from ArgumentNullException ("you shouldn't have passed in null as an argument - and it was for this parameter). I wish Java did the same thing, but even with just a NullPointerException, it's still much easier to fix code if the error is thrown at the earliest point at which it can be detected.

查看更多
家丑人穷心不美
6楼-- · 2020-01-30 03:14

Fail-fast

The code should crash as soon as possible. It should not do half of the work and then dereference the null and crash only then leaving half of some work done causing the system to be in an invalid state.

This is commonly called "fail early" or "fail-fast".

查看更多
祖国的老花朵
7楼-- · 2020-01-30 03:15

Because you can make things explicit by doing so. Like:

public class Foo {
  private final Bar bar;

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

Or shorter:

  this.bar = Objects.requireNonNull(bar, "bar must not be null");

Now you know:

  • when a Foo object was successfully created using new()
  • then its bar field is guaranteed be non-null.

Compare that to: you create a Foo object today, and tomorrow you invoke a method that uses that field and throws. Most likely, you will not know tomorrow why that reference was null yesterday when it got passed to the constructor!

In other words: by explicitly using this method to check incoming references you can control the point in time when the exception will be thrown. And most of the time, you want to fail as fast as possible!

The major advantages are:

  • as said, controlled behavior
  • easier debugging - because you throw up in the context of the object creation. At a point in time where you have a certain chance that your logs/traces tell you what went wrong!
  • and as shown above: the true power of this idea unfolds in conjunction with final fields. Because now any other code in your class can safely assume that bar isn't null - and thus you do not need any if (bar == null) checks in other places!
查看更多
登录 后发表回答