Почему 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() возвращает дополнительно, что имеет еще больше смысла для findFirst() чтобы иметь возможность обрабатывать null s.

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

5 56

5 ответов:

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

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

Броски:

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

как уже обсудили, разработчики 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" когда элемент не существует.

следующий код заменяет 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 лямбда не собирается ничего выделять.

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

что-то вроде

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

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

это возможный дизайн, чтобы разрешить необязательный с нулевым значением, без добавления логического флага-просто добавьте объект sentinel. (можно даже использовать this в качестве дозорного; видеть подобное.причина)

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

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

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

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

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

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