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


214

Я отметил, что многие методы Java 8 в Oracle JDK используют 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
Такой подход к проверке аргументов позволяет вам обманывать, когда вы пишете модульные тесты. Spring также имеет подобные утилиты (см. Docs.spring.io/spring/docs/current/javadoc-api/org/… ). Если у вас есть «если» и вы хотите иметь высокий охват модульным тестом, вы должны охватить обе ветви: когда условие выполнено и когда условие не выполнено. Если вы используете Objects.requireNonNull, ваш код не имеет разветвлений, поэтому за один проход модульного теста вы получите 100% охват :-)
xorcus

Ответы:


214

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

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()
  • тогда его поле бара гарантированно будет ненулевым.

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

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

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

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

23
Вы можете сделать свой код более компактным, написавthis.bar = Objects.requireNonNull(bar, "bar must not be null");
Кирилл Рахман

3
@KirillRakhman Обучение GhostCat чему-то клевому, о котором я не знал -> выиграть билет в лотерее GhostCat upvote.
GhostCat

1
Я думаю, что комментарий № 1 здесь фактическая выгода Objects.requireNonNull(bar, "bar must not be null");. Спасибо за это.
Каву

1
Какой был первым, и почему «языковые» люди должны следовать за авторами какой- то библиотеки ?! Почему гуава не следует за поведением Java lang ?!
GhostCat

1
Это this.bar = Objects.requireNonNull(bar, "bar must not be null");нормально в конструкторах, но потенциально опасно в других методах, если в одном и том же методе установлены две или более переменных. Пример: talkwards.com/2018/11/03/…
Эрк

100

Fail-быстрый

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

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


40

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

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

Рассматривать:

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

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


13

Использование в 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); 
Исключение в потоке "main" java.lang.NullPointerException
    в LookupService.isFirstElement (LookupService.java:5)
    в 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");
Исключение в потоке "main" java.lang.NullPointerException
    в java.util.Objects.requireNonNull (Objects.java:203)
    на com.Dictionary. (Dictionary.java:15)
    на com.Dictionary.main (Dictionary.java:24)

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


Вот Это Да! Отличное объяснение.
Джонатас Насименто

2

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

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

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

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

В основном это операция как: yourReference.getClass- которая потерпит неудачу, если ваша ссылка является null.

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

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

Или в принципе Objects.requireNotNull (yourReference)


1

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

Одна лучшей альтернативой (ярлык) для удовлетворения тех же требований является @NonNull аннотации на Ломбках.


1
Аннотация не является заменой метода Objects, они работают вместе. Вы не можете сказать @ NonNull x = mightBeNull, вы бы сказали @ NonNull x = Objects.requireNonNull (mightBeNull, "немыслимо!");
Билл К

@BillK извини, я не понимаю тебя
Supun Wijerathne

Я только что сказал, что аннотация Nonnull работает с requireNonNull, это не альтернатива, но они работают вместе довольно хорошо.
Билл К

реализовать провал быстрый характер, его альтернативное право? Да, я согласен, это не альтернатива для назначения.
Supun Wijerathne

Думаю, я бы назвал requireNonNull () «преобразованием» из @ Nullable в @ NonNull. Если вы не используете аннотации, метод на самом деле не очень интересен (поскольку все, что он делает, это бросает NPE, как это делает код, который он защищает) - хотя он довольно четко показывает ваше намерение.
Билл К

1

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


0

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


0

В контексте расширений компилятора, которые реализуют проверку Nullability (например: uber / NullAway ),Objects.requireNonNull следует использовать несколько экономно для случаев, когда у вас есть поле Nullable, которое, как вы знаете, не является нулевым в определенной точке вашего кода.

Таким образом, есть два основных использования:

  • Проверка

    • уже охвачены другими ответами здесь
    • проверка времени выполнения с накладными и потенциальными NPE
  • Маркировка обнуляемости (меняется с @Nullable на @Nonnull)

    • минимальное использование проверки во время выполнения в пользу проверки во время компиляции
    • работает только тогда, когда аннотации верны (применяется компилятором)

Пример использования маркировки Nullability:

@Nullable
Foo getFoo(boolean getNull) { return getNull ? null : new Foo(); }

// Changes contract from Nullable to Nonnull without compiler error
@Nonnull Foo myFoo = Objects.requireNonNull(getFoo(false));

0

В дополнение ко всем правильным ответам:

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

Просто пример: представьте, что у вас есть

<T> T parseAndValidate(String payload) throws ParsingException { ... };
<T> T save(T t) throws DBAccessException { ... };

где parseAndValidateоборачивает NullPointerExceptionиз requireNonNullв ParsingException.

Теперь вы можете решить, например, когда делать повторную попытку или нет:

...
.map(this::parseAndValidate)
.map(this::save)
.retry(Retry.<T>allBut(ParsingException.class))

Без проверки в saveметоде возникнет исключение NullPointerException , что приведет к бесконечным повторным попыткам. Даже стоит, представьте себе долгую живую подписку, добавив

.onErrorContinue(
    throwable -> throwable.getClass().equals(ParsingException.class),
    parsingExceptionConsumer()
)

Теперь RetryExhaustExceptionбудет убить вашу подписку.

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.