Зачем использовать Objects.requireNonNull()?

Я отметил, что многие методы Java 8 в Oracle JDK используют Objects.requireNonNull(), который внутренне Objects.requireNonNull() NullPointerException если заданный объект (аргумент) равен null.

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

Но NullPointerException будет выброшено в любом случае, если null объект разыменован. Итак, зачем делать эту дополнительную проверку на нуль и выбрасывать NullPointerException?

Один очевидный ответ (или преимущество) заключается в том, что он делает код более читабельным, и я согласен. Я хотел бы знать любые другие причины для использования Objects.requireNonNull() в начале метода.

Ответы

Ответ 1

Потому что вы можете сделать вещи явным, сделав это. Как:

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

Теперь вы знаете:

  • когда объект Foo был успешно создан с помощью new()
  • тогда его поле bar гарантировано будет непустым.

Сравните это с тем, что сегодня вы создаете объект Foo, а завтра вы вызываете метод, который использует это поле и выбрасывает. Скорее всего, вы не будете знать завтра, почему эта ссылка была вчера, когда она передана конструктору!

Другими словами: явно используя этот метод для проверки входящих ссылок, вы можете контролировать момент времени, когда будет выбрано исключение. И большую часть времени вы хотите сбой как можно быстрее!

Основные преимущества:

  • как сказано, контролируемое поведение
  • упрощенная отладка - потому что вы бросаете вызов в контексте создания объекта. В определенный момент, когда у вас есть определенный шанс, что ваши журналы/трассы расскажут вам, что пошло не так!
  • и как показано выше: истинная сила этой идеи разворачивается вместе с конечными полями. Поскольку теперь любой другой код в вашем классе может с уверенностью предположить, что bar не является нулевым - и, следовательно, вам не нужны никакие проверки if (bar == null) в других местах!

Ответ 2

Нормально-быстро

Код должен вылететь как можно скорее. Он не должен выполнять половину работы, а затем разыменовывать нулевое значение и падать только тогда, когда оставляется половина выполненной работы, в результате чего система находится в недопустимом состоянии.

Это обычно называется "провал рано" или "быстро провал".

Ответ 3

Но NullPointerException будет вызываться в любом случае, если нулевой объект разыменован. Итак, зачем нужно делать эту дополнительную проверку нулевого значения и бросать NullPointerException?

Это означает, что вы обнаруживаете проблему немедленно и надежно.

Рассмотрим:

  • Ссылка может использоваться не позже, чем позже, после того, как ваш код уже выполнил некоторые побочные эффекты.
  • Ссылка не может быть разыменована в этом методе вообще
    • Он может быть передан совершенно другому коду (т.е. причина и ошибка находятся далеко друг от друга в кодовом пространстве)
    • Его можно использовать гораздо позже (т.е. причина и ошибка далеки друг от друга во времени)
  • Он может быть использован где-то, где действительна нулевая ссылка, но имеет непреднамеренный эффект

.NET делает это лучше, разделив NullReferenceException ( "вы разыменовали нулевое значение" ) из ArgumentNullException ("вы не должны были передавать значение null в качестве аргумента), и это было для этого параметра). Java тоже делала то же самое, но даже с помощью NullPointerException, еще более легко исправить код, если ошибка возникает в самой ранней точке, где она может быть обнаружена.

Ответ 4

Использование requireNonNull() в качестве первых операторов в методе позволяет прямо/быстро определить причину исключения.
Трассировка стека четко указывает на то, что исключение было сгенерировано сразу после ввода метода , поскольку вызывающая сторона не соблюдала требования/контракт. Передача объекта null другому методу может действительно вызывать исключение за один раз, но причина проблемы может быть более сложной для понимания, поскольку исключение будет вызвано определенным вызовом объекта null, который может быть намного дальше.,


Вот конкретный и реальный пример, который показывает, почему мы должны отдавать предпочтение быстрым сбоям в целом и, в частности, используя Object.requireNonNull() или любой другой способ выполнить проверку на нулевое значение для параметров, предназначенных для null.

Предположим, что класс 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)

Исключение вызывается в классе LookupService, а его источник намного раньше (конструктор Dictionary). Это делает общий анализ проблем гораздо менее очевидным.
Является ли words null? Это words.get(0) null? Обе? Почему одно, другое или, может быть, оба null? Это ошибка кодирования в Dictionary (конструктор? Вызванный метод?)? Это ошибка кодирования в LookupService? (конструктор? вызванный метод?)?
Наконец, нам нужно будет проверить больше кода, чтобы найти источник ошибки, а в более сложном классе, возможно, даже использовать отладчик, чтобы легче понять, что это произошло.
Но почему простая вещь (отсутствие проверки нуля) становится сложной проблемой?
Потому что мы допустили начальную ошибку/отсутствие идентифицируемой при утечке конкретного компонента на нижних компонентах.
Представьте себе, что LookupService был не локальной службой, а удаленной службой или сторонней библиотекой с небольшим количеством отладочной информации, или представьте, что у вас не было 2, а 4 или 5 уровней вызовов объектов до того, как был обнаружен 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)

Обратите внимание, что здесь я проиллюстрировал проблему с помощью конструктора, но вызов метода мог иметь такое же ограничение проверки ненулевых значений.

Ответ 5

В качестве побочного примечания этот сбой быстро до Object#requireNotNull был реализован несколько иначе, чем java-9 внутри некоторых классов jre. Предположим, что случай:

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

В java-8 это компилируется как (только соответствующие части)

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

В основном операция as: yourReference.getClass - которая завершится с ошибкой, если ваша регрессия null.

Все изменилось в jdk-9, где тот же код компилируется как

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

Или в основном Objects.requireNotNull (yourReference)

Ответ 6

Основное использование - это проверка и бросок NullPointerException немедленно.

Одна из лучших альтернатив (ярлык) для удовлетворения того же требования - @NonNull аннотация от lombok.