Почему 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()
будет менее двусмысленным.
Ответы
Ответ 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();
(Говорят, что решение каждой проблемы ООП состоит в том, чтобы ввести другой тип:)