Подтвердить что ты не робот

Почему findFirst() выбрасывает исключение NullPointerException, если первый найденный элемент равен null?

Почему это выбрасывает java.lang.NullPointerException?

List<String> strings = new ArrayList<>();
        strings.add(null);
        strings.add("test");

        String firstString = strings.stream()
                .findFirst()      // Exception thrown here
                .orElse("StringWhenListIsEmpty");
                //.orElse(null);  // Changing the `orElse()` to avoid ambiguity

Первым элементом в strings является null, что является вполне приемлемым значением. Кроме того, findFirst() возвращает Optional, что еще более усугубляет возможность findFirst() обрабатывать null s.

EDIT: обновление orElse() будет менее двусмысленным.

4b9b3361

Ответ 1

Причиной этого является использование Optional<T> в возврате. Необязательно не разрешено содержать null. По существу, он не предлагает различать ситуации "он не там" и "он есть, но установлен на null".

Поэтому документация явно запрещает ситуацию, когда null выбран в findFirst():

Броски:

NullPointerException - если выбранный элемент null

Ответ 2

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

Если вы все еще хотите это сделать, вы можете сделать это явно, применив последовательность

.map(Optional::ofNullable).findFirst().flatMap(Function.identity())

в поток. В обоих случаях результат будет пустым, если первый элемент отсутствует или первый элемент null. Поэтому в вашем случае вы можете использовать

String firstString = strings.stream()
    .map(Optional::ofNullable).findFirst().flatMap(Function.identity())
    .orElse(null);

чтобы получить значение null, если первый элемент отсутствует или null.

Если вы хотите различать эти случаи, вы можете просто опустить шаг flatMap:

Optional<String> firstString = strings.stream()
    .map(Optional::ofNullable).findFirst().orElse(null);
System.out.println(firstString==null? "no such element":
                   firstString.orElse("first element is null"));

Это не сильно отличается от вашего обновленного вопроса. Вам просто нужно заменить "no such element" на "StringWhenListIsEmpty" и "first element is null" на null. Но если вам не нравятся условные обозначения, вы можете достичь этого также:

String firstString = strings.stream().skip(0)
    .map(Optional::ofNullable).findFirst()
    .orElseGet(()->Optional.of("StringWhenListIsEmpty"))
    .orElse(null);

Теперь firstString будет null, если элемент существует, но есть null, и он будет "StringWhenListIsEmpty", когда не существует элемента.

Ответ 3

Следующий код заменяет findFirst() на limit(1) и заменяет orElse() на reduce():

String firstString = strings.
   stream().
   limit(1).
   reduce("StringWhenListIsEmpty", (first, second) -> second);

limit() позволяет достигать только 1 элемента reduce. BinaryOperator, переданный в reduce, возвращает этот 1 элемент, или "StringWhenListIsEmpty", если никакие элементы не достигнут reduce.

Красота этого решения заключается в том, что Optional не выделяется, а BinaryOperator лямбда не собирается ничего выделять.

Ответ 4

Вы можете использовать java.util.Objects.nonNull для фильтрации списка, прежде чем найти

что-то вроде

list.stream().filter(Objects::nonNull).findFirst();

Ответ 5

Необязательно предполагается тип "значение". (прочитайте мелкий шрифт в javadoc:) JVM может даже заменить все Optional<Foo> на Foo, удалив все расходы на бокс и разблокировку. A null Foo означает пустой Optional<Foo>.

Это возможный дизайн, позволяющий Необязательный с нулевым значением, без добавления логического флага - просто добавьте объект-дозор. (может даже использовать this как дозорный, см. Throwable.cause)

Решение о том, что Необязательный не может переносить нуль, не зависит от стоимости исполнения. Это была чрезвычайно спорная проблема, и вам нужно копать списки рассылки. Решение не убеждает всех.

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

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

class Box<T>
    static Box<T> of(T value){ .. }

Optional<Box<String>> first = stream.map(Box::of).findFirst();

(Говорят, что решение каждой проблемы ООП состоит в том, чтобы ввести другой тип:)