Java8 Optional vs Stream中的嵌套空检查

时间:2019-04-07 21:57:22

标签: java java-8 java-stream

这是一个有关Streams行为可能令人困惑的问题。我的印象是.map操作(因为它在Optional中的用法)始终是null安全的(我知道这些 .map 是不同的实现,因为它们共享相同的名称)。当我在列表流中使用NPE时,我感到非常惊讶。从那时起,我开始将Objects :: nonNull与流一起使用(都与.map和.flatMap操作一起使用)。

Q1。 为什么Optional可以在任何级别处理空值,而Streams不能(在任何级别处理空值),如下面的测试代码所示?如果这是明智且理想的行为,请给出解释(关于它的好处或List Stream的缺点,如Optional)。

Q2。作为后续措施,除了我在下面的getValues方法中执行的过多空检查之外,还有其他选择(这促使我思考为什么Streams不能表现为Optional)。

在下面的测试中,我仅对最内层类的value字段感兴趣。

我在 getValue 方法中使用Optional。

我在 getValues 方法中使用列表中的流。而且在这种情况下,我无法删除单个nonNull检查。

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 个答案:

答案 0 :(得分:4)

  

Q1。如以下我的测试代码所示,为什么Optional可以在任何级别处理空值,而Streams不能(在任何级别)处理空值?

Stream可以“包含”空值。可选不能通过合同(合同在Javadoc中进行了解释):要么为空,并且map返回空,要么为非空,然后保证其具有非null值。

  

Q2。作为后续,我可以在下面的getValues方法中执行过多的空检查。

易于使用的设计,避免在整个地方使用null。

答案 1 :(得分:1)

  

Q1。为什么Optional可以在任何级别处理null,而为什么   如以下我的测试代码所示,流不能(任何级别)?

Optional的创建是为了处理自身的空值,即程序员不希望自己处理Null。 Optional.map()方法将值转换为Optional对象。这样就可以处理null并消除开发人员的责任。

另一方面,

Streams将空值的处理留给开发人员,即如果开发人员可能想以其他方式处理空值,该怎么办。查看此link。它为Stream的开发人员提供了不同的选择,并为每种情况提供了推理方式。

  

Q2。作为后续措施,是否有替代方法?   我在下面的getValues方法中执行的空检查

在您提到的用途中,如果您不想处理空值情况,请使用Optional。正如@JB Nizet所说,在使用流或自己处理流的情况下,请避免使用空方案。 This提出了类似的观点。如果您浏览我共享的第一个链接,您可能会从流中得到 Banning Null 太苛刻,而 Absorbing Null size()方法的真实性和其他功能。

答案 2 :(得分:1)

以下是您可以尝试的代码:

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);
      }
    }
  };
}

答案 3 :(得分:0)

您的Q1已由raviiii1和JB Nizet回答。 关于第二季度:

  

我可以在下面的getValues方法中执行过多的空检查吗?

您总是可以这样组合StreamOptional

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());
}