Зачем использовать 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.