代码之家  ›  专栏  ›  技术社区  ›  Jim C

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

  •  2
  • Jim C  · 技术社区  · 7 年前

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

    目的:我想有效地使用Java 8中的可选项,并避免使用检查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()));
    }
    

    我做了测试,我可以看到,只要我的家庭得到服务的回应,无论我是否有孩子,这都是完美的。我的意思是,在下面的服务实现中,如果我对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()));
    
        }
    

    对我来说,更干净的方法是以下方法之一(所有这些方法都失败了,但我相信它们可以显示出我一直在努力的方向):

    //在这里,我尝试在映射之前过滤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()));
    
    }
    

    我知道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

    <?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>
    

    服务实施的相关部分:

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

    因此,我的直接问题是:根据我上面描述的wsdl场景及其实现,通过使用isPresent()来检查web服务返回是否为null的真正唯一方法是否与我们使用经典null检查(if(f!=null){…)的方法非常相似?

    2 回复  |  直到 7 年前
        1
  •  6
  •   Holger    7 年前

    主要的误解是认为有必要进行如下操作 .filter(Objects::nonNull) 在可选的上。如果对空期权有必要进行过滤,那么这将破坏期权的全部目的。尤其是,当谓词的计算结果为 false ,让你回到原点。

    事实上 .筛选器(对象::非Null) 具有与相同的效果 .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 . 您还包括了一个新的 无效的 签入流操作,该操作将处理子实例 无效的 .

    如果真的需要这样做,那么最好的解决方案将是对负责服务实现的人进行抨击,但无论如何,第二个最好的解决方案是

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

    这比插入额外的 .筛选器(对象::非Null) 进入小溪。

        2
  •  2
  •   fps    7 年前

    用于处理结果 Optional ,请参阅 Holger's answer . 在这里,我想采取一种不同的方法。

    问自己一个问题:为什么你需要 可选择的 来处理这个案子?是否要避免对 if 阻止返回的 Family 价值

    考虑以下代码:

    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.flatMap . 该流是逐步处理的,而不是作为一个线性。在 可选择的 是空安全的(我的意思是,我不仅要检查返回的首字母 家庭 实例为 null ,也就是如果 f.getChildren() 退货 无效的 以及)。

    哪个版本较短?哪一个更优雅?哪一个更清晰、更容易理解?哪一种表达程序员意图的方式最好?

    我知道我的答案。。。