这是一个有关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]
答案 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方法中执行过多的空检查吗?
您总是可以这样组合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());
}