Lambda 如何仅使用高阶函数来检查web服务的返回是否为null(使用Optional)并避免NoTouchElementException
上下文:我有一个web服务,它返回一个家庭及其成员。一个家庭总是有一个父亲和一个母亲,没有孩子或多个孩子。服务的wsdl描述如下 目的:我想有效地使用Java8中的可选项,并避免经典的检查null的方法。我所说的经典,是指在Java7之前我们一直使用的实现方式 如果我假设Web服务总是返回一个家庭,这就足够了:Lambda 如何仅使用高阶函数来检查web服务的返回是否为null(使用Optional)并避免NoTouchElementException,lambda,java-8,stream,optional,higher-order-functions,Lambda,Java 8,Stream,Optional,Higher Order Functions,上下文:我有一个web服务,它返回一个家庭及其成员。一个家庭总是有一个父亲和一个母亲,没有孩子或多个孩子。服务的wsdl描述如下 目的:我想有效地使用Java8中的可选项,并避免经典的检查null的方法。我所说的经典,是指在Java7之前我们一直使用的实现方式 如果我假设Web服务总是返回一个家庭,这就足够了: @Test public void test1() { Family f = helloWorldClientImplBean.allFamily(); f.getCh
@Test
public void test1() {
Family f = helloWorldClientImplBean.allFamily();
f.getChildren().stream().filter(x -> x.getFirstName().equalsIgnoreCase("John")).findFirst()
.ifPresent(y -> System.out.println(y.getLastName()));
}
我做了测试,我可以看到,只要我得到一个家庭的服务,无论我是否有孩子,它都能完美地工作。我的意思是,在下面的服务实现中,如果我对olderSon和youngSon代码进行了注释,将不会出现任何空异常
当服务返回null时会出现问题
在阅读了几篇博客并进行了讨论之后,我找到了这段代码,它正确地检查了服务返回是否为null
@Test
public void testWorkingButSeemsOdd() {
//Family f = helloWorldClientImplBean.allFamily();
Family f = null; //to make simple the explanation
Optional<Family> optFamily = Optional.ofNullable(f);
if (optFamily.isPresent()) {
optFamily.filter(Objects::nonNull).map(Family::getChildren).get().stream().filter(Objects::nonNull)
.filter(x -> x.getFirstName().equalsIgnoreCase("John")).findFirst()
.ifPresent(y -> System.out.println("Optional: " + y.getLastName()));
}
我知道next无法编译,但我想可能会达到类似的效果
@Test
@Ignore
public void testOptionalNullable() {
Family f = helloWorldClientImplBean.allFamily();
Optional.ofNullable(f).orElse(System.out.println("Family is null")).map(Family::getChildren).get().stream().filter(Objects::nonNull)
.filter(x -> x.getFirstName().equalsIgnoreCase("John")).findFirst()
.ifPresent(y -> System.out.println(y.getLastName()));
}
wsdl
因此,我的直截了当的问题是:根据我在上面描述的wsdl场景及其实现,使用isPresent()检查web服务返回是否为null的唯一方法是什么?与我们使用经典的null检查(if(f!=null){…)的方法非常类似?主要的误解是认为需要执行
.filter(Objects::nonNull)之类的操作
在可选项上。如果空可选项需要过滤,则会破坏可选项的整个用途。特别是,当谓词的计算结果为false
时,过滤的结果再次是空可选项,使您回到原点
事实上,.filter(Objects::nonNull)
与.filter(x->true)
具有相同的效果,对于非空选项,它总是true
,对于空选项,它无论如何都不会得到计算
此外,您正在切换回if
语句,尽管您已经知道ifPresent
Optional.ofNullable(helloWorldClientImplBean.allFamily())
.ifPresent(f -> f.getChildren().stream()
.filter(x -> x.getFirstName().equalsIgnoreCase("John"))
.findFirst()
.ifPresent(y -> System.out.println(y.getLastName()));
通过将操作更改为,可以减少嵌套零件
Optional.ofNullable(helloWorldClientImplBean.allFamily())
.flatMap(f -> f.getChildren().stream()
.filter(x -> x.getFirstName().equalsIgnoreCase("John"))
.findFirst())
.ifPresent(y -> System.out.println(y.getLastName()));
这只解决了您在问题中描述的问题,即服务allFamily()
可能返回null
。您还将新的null
检查包含到流操作中,该操作将处理子实例为null
的情况
如果确实需要这样做,那么最好的解决方案就是踢负责服务实现的人的屁股,但无论如何,第二个最好的解决方案就是简单地这样做
Optional.ofNullable(helloWorldClientImplBean.allFamily())
.flatMap(f -> f.getChildren().stream()
.filter(x -> x!=null && x.getFirstName().equalsIgnoreCase("John"))
.findFirst())
.ifPresent(y -> System.out.println(y.getLastName()));
这比在流中插入一个附加的
.filter(Objects::nonNull)
更简单。有关使用Optional
处理结果的信息,请参阅。在这里,我想采用另一种方法
问自己一个问题:为什么需要Optional
来处理这种情况?是为了避免对返回的Family
值使用带有if
块的空检查吗
考虑以下代码:
Family f = helloWorldClientImplBean.allFamily();
if (f != null) {
f.getChildren().stream()
.filter(x -> x.getFirstName().equalsIgnoreCase("John"))
.findFirst()
.ifPresent(y -> System.out.println(y.getLastName()));
}
它非常清晰,易于阅读和维护
现在考虑,即这样的代码:
Optional.ofNullable(helloWorldClientImplBean.allFamily())
.map(Family::getChildren)
.map(Collection::stream)
.map(stream -> stream.filter(x -> "John".equalsIgnoreCase(x.getFirstName())))
.flatMap(Stream::findFirst)
.map(Persontype::getLastName)
.ifPresent(System.out::println);
此代码是函数式的。所有操作都通过Optional.map
操作在Optional
上执行,但返回流的第一个元素的操作除外,该操作是通过Optional.flatMap
完成的。流是一步一步地处理的,而不是作为一个线性行。执行的每个操作都是呃可选
是空安全的(我的意思是,我不仅要检查返回的初始系列
实例是否为null
,也就是说,如果f.getChildren()
也返回null
)
哪个版本更短?哪个版本更优雅?哪个版本更清晰更容易理解?哪个版本以最好的方式表达了程序员的意图
我知道我的答案…我认为这是一个基于观点的问题…你可以有
if-else
和可选。如果是空的,这只是一个选择问题。问题是我会选择if/else
和{
block,对于meCalling.filter(Objects::nonNull),它看起来更干净Optional
上的
没有任何意义。Optional
的要点是封装的对象永远不能为null
。当Optional为空时,将永远不会计算筛选谓词。因此.filter(Objects::nonNull)
的效果与filter(x->true)相同
,在这两种情况下,您都可以返回原始的可选项。如果可能,我建议修改helloWorldClientImplBean.allFamily()
返回Optional
而不是可为空的系列
引用。与其将结果包装在Optional.ofNullable
中,不如将Optional
的创建推送到被调用方中,以便此处显示的调用代码更适合直接使用Optional
。这是可以强制呼叫者执行空检查或自己的可选换行。@Stuart Marks,谢谢。非常有用的说明。在编写真实场景时,我会记住这一点。老实说,霍尔格完全回答了我的问题,但他和你在我的学习过程中都添加了很多内容。但是,自从你问我以后“为什么您需要可选的来处理这个案例?”,我想返回另一个问题:我上面的案例非常简单,但是如果我有更复杂的嵌套类型,那么我现在正在编写一个客户端,它将调用政府服务和所有服务
Optional.ofNullable(helloWorldClientImplBean.allFamily())
.flatMap(f -> f.getChildren().stream()
.filter(x -> x!=null && x.getFirstName().equalsIgnoreCase("John"))
.findFirst())
.ifPresent(y -> System.out.println(y.getLastName()));
Family f = helloWorldClientImplBean.allFamily();
if (f != null) {
f.getChildren().stream()
.filter(x -> x.getFirstName().equalsIgnoreCase("John"))
.findFirst()
.ifPresent(y -> System.out.println(y.getLastName()));
}
Optional.ofNullable(helloWorldClientImplBean.allFamily())
.map(Family::getChildren)
.map(Collection::stream)
.map(stream -> stream.filter(x -> "John".equalsIgnoreCase(x.getFirstName())))
.flatMap(Stream::findFirst)
.map(Persontype::getLastName)
.ifPresent(System.out::println);