У меня есть этот фрагмент кода
Coverage mainCoverage = illus.getLifes().stream()
.filter(Life::isIsmain)
.findFirst()
.orElseThrow(() -> new ServiceInvalidAgurmentGeneraliException(env.getProperty("MSG_002")))
.getCoverages()
.stream() // <==may cause null here if list coverage is null
.filter(Coverage::isMainplan)
.findFirst()
.orElseThrow(() -> new ServiceInvalidAgurmentGeneraliException(env.getProperty("MSG_002")));
который полностью работает нормально, но я думаю, что это немного беспорядочно и не охватывает все возможные null pointer exception (см. комментарий).
Я пытаюсь преобразовать этот код в
Coverage mainCoverage1 = illus.getLifes().stream()
.filter(Life::isIsmain)
.map(Life::getCoverages)
.filter(Coverage::isMainplan) //<== cannot filter from list coverage to one main coverage
...
Кажется, после того, как я сопоставлю жизнь с покрытием, это больше не список покрытия. Итак, вопрос в том, как я могу преобразовать первый раздел в нулевой безопасный и, возможно, сократить его?




Life::getCoverages возвращает коллекцию, поэтому фильтр Coverage::isMainplan не будет работать, вместо этого вы должны flatMap последовательности, возвращенные после .map(Life::getCoverages), а затем применить операцию filter к Coverage:
Coverage mainCoverage =
illus.getLifes()
.stream()
.filter(Life::isIsmain)
.map(Life::getCoverages)
//.filter(Objects::nonNull) uncomment if there can be null lists
.flatMap(Collection::stream) // <--- collapse the nested sequences
//.filter(Objects::nonNull) // uncomment if there can be null Coverage
.filter(Coverage::isMainplan)
.findFirst().orElse(...);
Я добавил в ваш код несколько вещей:
.filter(Objects::nonNull) после .map(Life::getCoverages), который вы можете раскомментировать, учитывая, что возвращаемые элементы потенциально могут быть нулевыми..flatMap(Collection::stream), который возвращает поток, состоящий из результатов замены каждого элемента этого потока содержимым сопоставленного потока, созданного путем применения предоставленной функции сопоставления к каждому элементу..filter(Objects::nonNull), который вы можете раскомментировать, учитывая, что элементы, возвращаемые после flatMap, потенциально могут быть нулевыми..filter(Coverage::isMainplan) и, наконец, получить первый объект, удовлетворяющий критериям, через findFirst, а если нет, то предоставить значение по умолчанию через orElse.Я бы посоветовал взглянуть на следующие блоги, чтобы познакомиться с методом flatMap:
В первой части вашего кода вы можете вставить filter(e -> e != null), чтобы не быть уверенным, что List равен нулю, он не выдаст NPE:
Coverage mainCoverage = illus.getLifes().stream()
.filter(Life::isIsmain)
.findFirst()
.orElseThrow(() -> new ServiceInvalidAgurmentGeneraliException(env.getProperty("MSG_002")))
.getCoverages()
.filter(e -> e != null) //<=== Filter out all null values
.stream()
.filter(Coverage::isMainplan)
.findFirst()
.orElseThrow(() -> new ServiceInvalidAgurmentGeneraliException(env.getProperty("MSG_002"))
Проблема с вашим вторым фрагментом кода заключается в том, что я предполагаю, что Life::getCoverages возвращает Collection, а не отдельные объекты Coverage, поэтому вы не можете вызвать Coverage::isMainplan на нем.
вы все еще ведете потоковую передачу через Life#getCoverages, что может привести к выбросу NPE.
@HPH Я отфильтровываю все нулевые значения, поэтому, даже если List::getCoverages возвращает null, это не будет проходить поверх нулевых значений
вы все еще ошибаетесь, вы не можете передавать поток через коллекцию null, коллекция в данном случае - это life.getCoverages(), где life - это первый ненулевойLife, чей isIsmain() возвращает true.
Добавьте условие в filter, если список не равен нулю и i.isIsmain, тогда только фильтр, вы можете использовать public static boolean isNull(Object obj) или public static boolean nonNull(Object obj)
Coverage mainCoverage = illus.getLifes().stream()
.filter(i->i.isIsmain && Objects.nonNull(i.getCoverages()))
.findFirst()
.orElseThrow(() -> new ServiceInvalidAgurmentGeneraliException(env.getProperty("MSG_002")))
.getCoverages()
.stream() // <==may cause null here if list coverage is null
.filter(Coverage::isMainplan)
.findFirst()
.orElseThrow(() -> new ServiceInvalidAgurmentGeneraliException(env.getProperty("MSG_002")));
Вы можете попытаться инкапсулировать полученный Collection<Coverage> в Optional<Collection<Coverage>>, чтобы можно было отображать его способом нулевой сейф.
final Supplier<ServiceInvalidAgurmentGeneraliException> customExceptionThrower = () -> new ServiceInvalidAgurmentGeneraliException(env.getProperty("MSG_002"));
final Collection<Coverage> firstMainLifeCoverages = illus.getLifes().stream()
.filter(Life::isIsmain)
.findFirst()
.orElseThrow(customExceptionThrower)
.getCoverages();
Optional.ofNullable(firstMainLifeCoverages)
.map(Collection::stream)
.orElseThrow(customExceptionThrower)
.filter(Coverage::isMainplan)
.findFirst()
.orElseThrow(customExceptionThrower);
Приведенный вами пример и приведенные здесь ответы нарушают некоторые принципы программирования в чистом функциональном стиле.
Во-первых, вам не следует смешивать действия терминала и снова создавать потоки в одной и той же цепочке / конвейере кода. Как в вашем примере stream (). findFirst (). orElseThrow (..). stream (). otherActions. Это действительно не очень хорошая практика и подвержена ошибкам. В идеале цепочка вызовов Java Stream API должна работать с одним Stream. Так будет легче следить за своим кодом и рассуждать о нем.
Во-вторых, вы упомянули, что этот бит может поразить Null Pointer exc:
.getCoverages()
.stream() // <==may cause null here if list coverage is null
если getCoverages () должен возвращать коллекцию, она никогда не должна возвращать null, вместо этого всегда возвращать пустую коллекцию.
Разбейте код на отдельные логические части и дайте ему несколько значимых имен:
Coverage mainCoverage = illus.getLifes().stream()
.filter(Life::isIsmain)
.findFirst()
.orElseThrow(() -> new ServiceInvalidAgurmentGeneraliException(env.getProperty("MSG_002")));
Coverage mainplan = mainCoverage.getCoverages().stream()
.filter(Coverage::isMainplan)
.findFirst()
.orElseThrow(() -> new ServiceInvalidAgurmentGeneraliException(env.getProperty("MSG_002")));
Это выглядит намного лучше, чем ваша отправная точка. Надеюсь, это поможет.
хотя ваш ответ не дает прямого ответа на мой вопрос, но +1, чтобы хорошо объяснить и показать мне настоящую проблему моего кода.
Я предполагаю, что
Life::getCoveragesвозвращаетCollection, а не отдельные объектыCoverage.