Lambda 如何仅使用高阶函数来检查web服务的返回是否为null(使用Optional)并避免NoTouchElementException

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

上下文:我有一个web服务,它返回一个家庭及其成员。一个家庭总是有一个父亲和一个母亲,没有孩子或多个孩子。服务的wsdl描述如下

目的:我想有效地使用Java8中的可选项,并避免经典的检查null的方法。我所说的经典,是指在Java7之前我们一直使用的实现方式

如果我假设Web服务总是返回一个家庭,这就足够了:

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