Вложенные нулевые проверки в Java8 Необязательно против потока - PullRequest
0 голосов
/ 08 апреля 2019

Это вопрос относительно возможного запутанного поведения потоков. У меня сложилось впечатление, что .map операция (из-за использования в Optional) всегда была нулевой (я знаю, что эти .map - это разные реализации, хотя они и имеют одно и то же имя) , И я был весьма удивлен, когда получил NPE, когда использовал его в потоке (список). С тех пор я начал использовать Objects :: nonNull с потоками (оба с операциями .map и .flatMap).

Q1. Почему Optional может обрабатывать пустые значения на любом уровне, тогда как Streams не может (на любом уровне), как показано в моем тестовом коде ниже? Если это разумное и желательное поведение, пожалуйста, объясните (что касается его преимуществ или недостатков List Stream, которые ведут себя как необязательные).

Q2. Как следствие, есть альтернатива чрезмерным нулевым проверкам, которые я выполняю в методе getValues ​​ниже (именно это побудило меня подумать, почему Streams не может вести себя как Optional).

В приведенном ниже тесте меня интересует только поле значения самого внутреннего класса.

Я использую Optional в getValue методе.

Я использую Streams on list в getValues ​​ методе. И в этом случае я не могу удалить ни одной проверки NULL.

import lombok.AllArgsConstructor;
import lombok.Getter;

import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.stream.Collectors;

public class NestedObjectsStreamTest {
    @Getter @AllArgsConstructor
    private static class A {
        private B b;
    }
    @Getter @AllArgsConstructor
    private static class B {
        private C c;
    }
    @Getter @AllArgsConstructor
    private static class C {
        private D d;
    }
    @Getter @AllArgsConstructor
    private static class D {
        private String value;
    }

    public static void main(String[] args) {
        A a0 = new A(new B(new C(new D("a0"))));
        A a1 = new A(new B(new C(new D("a1"))));
        A a2 = new A(new B(new C(new D(null))));
        A a3 = new A(new B(new C(null)));
        A a5 = new A(new B(null));
        A a6 = new A(null);
        A a7 = null;

        System.out.println("getValue(a0) = " + getValue(a0));
        System.out.println("getValue(a1) = " + getValue(a1));
        System.out.println("getValue(a2) = " + getValue(a2));
        System.out.println("getValue(a3) = " + getValue(a3));
        System.out.println("getValue(a5) = " + getValue(a5));
        System.out.println("getValue(a6) = " + getValue(a6));
        System.out.println("getValue(a7) = " + getValue(a7));

        List<A> aList = Arrays.asList(a0, a1, a2, a3, a5, a6, a7);

        System.out.println("getValues(aList) " + getValues(aList));
    }

    private static String getValue(final A a) {
        return Optional.ofNullable(a)
            .map(A::getB)
            .map(B::getC)
            .map(C::getD)
            .map(D::getValue)
            .orElse("default");
    }

    private static List<String> getValues(final List<A> aList) {
        return aList.stream()
            .filter(Objects::nonNull)
            .map(A::getB)
            .filter(Objects::nonNull)
            .map(B::getC)
            .filter(Objects::nonNull)
            .map(C::getD)
            .filter(Objects::nonNull)
            .map(D::getValue)
            .filter(Objects::nonNull)
            .collect(Collectors.toList());
    }
}

выход

getValue(a0) = a0
getValue(a1) = a1
getValue(a2) = default
getValue(a3) = default
getValue(a5) = default
getValue(a6) = default
getValue(a7) = default

getValues(aList) [a0, a1]

Ответы [ 4 ]

4 голосов
/ 08 апреля 2019

Q1. Почему Optional может обрабатывать пустые значения на любом уровне, а Streams не может (на любом уровне), как показано в моем тестовом коде ниже?

Поток может "содержать" нулевые значения. Необязательный не может, по контракту (контракт объясняется в javadoc): либо он пустой, и карта возвращает пустое, либо он не пустой, а затем гарантированно будет иметь ненулевое значение.

Q2. Как продолжение, есть ли альтернатива чрезмерным проверкам нуля, которые я выполняю в методе getValues ​​ниже.

Любите проекты, которые избегают использования нулей повсюду.

1 голос
/ 08 апреля 2019

Вот коды, которые вы можете попробовать:

aList.stream()
    .map(applyIfNotNull(A::getB))
    .map(applyIfNotNull(B::getC))
    .map(applyIfNotNull(C::getD))
    .map(applyIfNotNullOrDefault(D::getValue, "default"))
    .filter(Objects::nonNull)
    .forEach(System.out::println);

С помощью следующих служебных методов:

public static <T, U> Function<T, U> applyIfNotNull(Function<T, U> mapper) {
  return t -> t != null ? mapper.apply(t) : null;
}

public static <T, U> Function<T, U> applyIfNotNullOrDefault(Function<T, U> mapper, U defaultValue) {
  return t -> t != null ? mapper.apply(t) : defaultValue;
}

public static <T, U> Function<T, U> applyIfNotNullOrElseGet(Function<T, U> mapper, Supplier<U> supplier) {
  return t -> t != null ? mapper.apply(t) : supplier.get();
}

Не уверен, как это выглядит для вас. но лично мне не нравится map(...).map(...).... Вот что мне нравится больше:

aList.stream()
    .map(applyIfNotNull(A::getB, B::getC, C::getD))
    .map(applyIfNotNullOrDefault(D::getValue, "default"))
    .filter(Objects::nonNull)
    .forEach(System.out::println);

С помощью еще одного служебного метода:

public static <T1, T2, T3, R> Function<T1, R> applyIfNotNull(Function<T1, T2> mapper1, Function<T2, T3> mapper2,
    Function<T3, R> mapper3) {
  return t -> {
    if (t == null) {
      return null;
    } else {
      T2 t2 = mapper1.apply(t);
      if (t2 == null) {
        return null;
      } else {
        T3 t3 = mapper2.apply(t2);
        return t3 == null ? null : mapper3.apply(t3);
      }
    }
  };
}
1 голос
/ 08 апреля 2019

Q1. Почему Optional может обрабатывать нули на любом уровне, тогда как Потоки не могут (на любом уровне), как показано в моем тестовом коде ниже?

Optional был создан для обработки нулевых значений сам по себе, то есть когда программист не хотел обрабатывать нулевые значения самостоятельно. Метод Optional.map() преобразует значение в объект Optional. Таким образом, обработка нулей и снятие ответственности с разработчиков.

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

Q2. Как продолжение, есть ли альтернатива чрезмерному нулевые проверки, которые я выполняю в методе getValues ​​ниже

В тех случаях, когда вы упомянули, что вы не хотите обрабатывать нулевые случаи, используйте Optional. Как сказано @JB Nizet, избегайте нулевых сценариев в случае использования потоков или обрабатывайте их самостоятельно. Это утверждает аналогично. Если вы перейдете по первой ссылке, которой я поделился, вы, вероятно, получите, что Banning Null из потока будет слишком резким, а Absorbing Null будет препятствовать достоверности size() метод и другие функциональные возможности.

0 голосов
/ 08 апреля 2019

На ваш Q1 уже ответили raviiii1 и JB Nizet.Относительно вашего Q2:

есть ли альтернатива чрезмерным нулевым проверкам, которые я выполняю в методе getValues ​​ниже

Вы всегда можете объединить Stream и Optional вот так:

private static List<String> getValues(final List<A> aList) {
    return aList.stream()
            .map(Optional::ofNullable)
            .map(opta -> opta.map(A::getB))
            .map(optb -> optb.map(B::getC))
            .map(optc -> optc.map(C::getD))
            .map(optd -> optd.map(D::getValue))
            .map(optv -> optv.orElse("default"))
            .collect(Collectors.toList());
}

Конечно, это будет намного чище:

private static List<String> getValues(final List<A> aList) {
    return aList.stream()
            .map(NestedObjectsStreamTest::getValue)
            .collect(Collectors.toList());
}
...