如何使用高阶函数来检查Web服务的返回是否为null使用Optional并避免noSuchElementException

Jim*_*m C 2 lambda stream optional higher-order-functions java-8

上下文:我有一个Web服务,它返回一个带有menbers的家庭.一个家庭将永远有一个父亲和一个母亲,没有孩子或多个孩子.该服务在下面由wsdl描述.

目的:我想有效地使用Java 8中的Optional,并避免使用经典方法来检查null.经典,我的意思是我们习惯于在Java 7之前完成的方式.

如果我假设webservice将始终返回一个家庭,这就足够了:

@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()));
}
Run Code Online (Sandbox Code Playgroud)

我测试了,我可以看到,只要我得到一个家庭通过服务回答,如果我有孩子,这将完全无关紧要.我的意思是,在下面的服务实现中,如果我评论了oldSon和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()));

    }
Run Code Online (Sandbox Code Playgroud)

对我来说更干净的是这些方法中的一种(所有这些方法都令人费解,但我相信它们可以展示我一直在尝试做的事情):

//这里我尝试在映射之前过滤f是否为null

@Test
public void testFilterNonNull() {
    Family f = null;
    Optional.ofNullable(f).filter(Objects::nonNull).map(Family::getChildren).get().stream().filter(Objects::nonNull)
            .filter(x -> x.getFirstName().equalsIgnoreCase("John")).findFirst()
            .ifPresent(y -> System.out.println(y.getLastName()));

}
Run Code Online (Sandbox Code Playgroud)

我知道下一个没有编译,但我想有可能达到类似的东西

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

}
Run Code Online (Sandbox Code Playgroud)

WSDL

<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<wsdl:definitions targetNamespace="http://codenotfound.com/services/helloworld"
    xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/" xmlns:tns="http://codenotfound.com/services/helloworld"
    xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/" xmlns:xsd="http://www.w3.org/2001/XMLSchema"
    name="HelloWorld">

    <wsdl:types>
        <schema targetNamespace="http://codenotfound.com/services/helloworld"
            xmlns="http://www.w3.org/2001/XMLSchema" xmlns:tns="http://codenotfound.com/services/helloworld"
            elementFormDefault="qualified" attributeFormDefault="unqualified"
            version="1.0">

            <element name="family">
                <complexType>
                    <sequence>
                        <element name="father" type="tns:persontype" minOccurs="1"
                            maxOccurs="1" />
                        <element name="mother" type="tns:persontype" minOccurs="1"
                            maxOccurs="1" />
                        <element name="children" type="tns:persontype" minOccurs="0"
                            maxOccurs="unbounded" />
                    </sequence>
                </complexType>
            </element>

            <complexType name="persontype">
                <sequence>
                    <element name="firstName" type="xsd:string" />
                    <element name="lastName" type="xsd:string" />
                </sequence>
            </complexType>

            <element name="EmptyParameter" type="tns:voidType" />

            <complexType name="voidType">
                <sequence />
            </complexType>
        </schema>
    </wsdl:types>

    <!-- Message -->

    <wsdl:message name="emptyRequest">
        <wsdl:part name="emptyParameter" element="tns:EmptyParameter" />
    </wsdl:message>

    <wsdl:message name="allFamiliesResponse">
        <wsdl:part name="allFamiliesResponse" element="tns:family" />
    </wsdl:message>

    <!-- PortType -->

    <wsdl:operation name="allFamilies">
            <wsdl:input message="tns:emptyRequest" />
            <wsdl:output message="tns:allFamiliesResponse"></wsdl:output>
        </wsdl:operation>
    </wsdl:portType>

    <!-- Binding -->

    <wsdl:binding name="HelloWorld_Binding" type="tns:HelloWorld_PortType">
        <soap:binding style="document"
            transport="http://schemas.xmlsoap.org/soap/http" />

        <wsdl:operation name="allFamilies">
            <wsdl:input>
                <soap:body use="literal" />
            </wsdl:input>
            <wsdl:output>
                <soap:body use="literal" />
            </wsdl:output>
        </wsdl:operation>
    </wsdl:binding>

    <wsdl:service name="HelloWorld_Service">
        <wsdl:port name="HelloWorld_Port" binding="tns:HelloWorld_Binding">
            <soap:address location="http://localhost:9090/cnf/services/helloworld" />
        </wsdl:port>
    </wsdl:service>
</wsdl:definitions>
Run Code Online (Sandbox Code Playgroud)

服务实施的相关部分:

@Override
public Family allFamilies(VoidType emptyParameter) {
    ObjectFactory factory = new ObjectFactory();
    Family result = factory.createFamily();
    Persontype father = new Persontype();
    father.setFirstName("Jose");
    father.setLastName("Pereira");

    Persontype mother = new Persontype();
    mother.setFirstName("Maria");
    mother.setLastName("Pereira");

    result.setFather(father);
    result.setMother(mother);


    Persontype olderSon = new Persontype();
    olderSon.setFirstName("John");
    olderSon.setLastName("Pereira");

    Persontype youngerSon = new Persontype();
    youngerSon.setFirstName("Ana");
    youngerSon.setLastName("Pereira");
    result.getChildren().add(olderSon);
    result.getChildren().add(youngerSon);

    return result;
}
Run Code Online (Sandbox Code Playgroud)

所以,我的直接问题是:基于WSDL和其实施上述我的方案,是检查是否从Web服务返回的是空在我们习惯做的一个非常相似的方式使用isPresent()真正唯一途径经典的空检查(if(f!= null){...)?

Hol*_*ger 6

主要的误解是假设需要.filter(Objects::nonNull)在可选项上执行操作.如果空选项需要这样的过滤,那将会击败期权的全部目的.特别是,当谓词评估时,过滤的结果再次是一个空的可选项false,使你回到原点1.

事实上,.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()));
Run Code Online (Sandbox Code Playgroud)

您可以通过将操作更改为减少嵌套部分

Optional.ofNullable(helloWorldClientImplBean.allFamily())
        .flatMap(f -> f.getChildren().stream()
            .filter(x -> x.getFirstName().equalsIgnoreCase("John"))
            .findFirst())
        .ifPresent(y -> System.out.println(y.getLastName()));
Run Code Online (Sandbox Code Playgroud)

这只解决了您在问题中描述的问题,即服务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()));
Run Code Online (Sandbox Code Playgroud)

这比.filter(Objects::nonNull)在流中插入附加内容更简单.

  • 另外还有一个"为负责服务实施的人提供支持". (4认同)
  • 合理的API应该返回一个空列表而不是"null".但是我的答案的第一部分已经解决了这个问题; 我的回答的第二部分是指你的`null`检查应用于流元素,只有当列表不是'null`,而不是空,但包含`null`元素时才需要.这是导致我对服务实现者的评论的更糟糕的罪犯,*如果*这样的场景真的可能.如果没有,我的答案的第一部分的解决方案已经足够了.虽然返回空集合而不是"null"仍然是优选的. (2认同)