2017-12-14 29 views
2

컨텍스트 : 나는 menbers가있는 패밀리를 반환하는 웹 서비스가 있습니다. 가족에게는 항상 아버지와 어머니, 자녀가 없거나 여러 개의 자녀가 있습니다. 서비스는 wsdl에 의해 아래에서 설명됩니다.웹 서비스의 반환이 Optional을 사용하여 null인지 확인하고 noSuchElementException을 피하는 방법을 고차 함수를 사용하는 방법

목적 : Java 8에서 선택 사항을 효과적으로 사용하고 null을 확인하는 고전적인 방법을 사용하지 않겠습니다.

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

내가 테스트하고 나는 것을 볼 수 있습니다 : 고전, 나는 우리가 내가 웹 서비스는 항상 가족을 반환한다고 가정하면 자바 7

까지 달성하는 데 사용 된 방법을 의미하는이 충분하다 , 나는 가족이 봉사함으로써 응답을받는 한, 나는 자녀가 있는지 없는지 상관없이 완벽하게 작동 할 것입니다. 나는 아래의 서비스 구현에서 oldSon 및 youngSon 코드에 null 예외가 전혀 없다고 주석을 달았습니다.

서비스가 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())); 

    } 

나 이러한 방법 중 하나 (모두가 failling하고 있지만 나는 그들이 내가 할 노력하고 무엇을 보여줄 수 있다고 생각) 것 더 깨끗한 것이 무엇 : 여기에 내가 시도

// f는 매핑

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

} 

내가 컴파일되지 않습니다 다음을 알고 있지만 나는 비슷한에 도달 할 수 있습니다 생각하기 전에 null가 아닌 경우, 필터링하는

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

} 
01 23,516,

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 및 구현 전술 내 시나리오에 따라이 있는지 확인하기 위해 정말 유일한 방법입니다 우리가 고전적인 null 검사 (if (f! = null) {...)와 매우 유사한 방식으로 isPresent()를 사용하여 웹 서비스로부터의 반환 값은 null이됩니까?

+2

나는 여기입니다 의견을 기반으로 질문 ... 당신은'IF-else' * 아니라-*은'Optional.ofNullable'로, 그것의 단지를 가지고 있다고 생각 선택의 문제. 문제는'if/else'와'{}'블록 내부의 스트림 작업에 대한 것이고, 어떻게 든 더 깨끗해 보입니다. – Eugene

+5

'Optional'에'.filter (Objects :: nonNull)'를 호출하면 어떤 의미가 있습니다. 'Optional'의 요점은 캡슐화 된 객체가 절대 null이 될 수 없다는 것입니다. 선택적 요소가 비어 있으면 필터 조건자가 평가되지 않습니다. 그래서'.filter (Objects :: nonNull)'의 효과는'.filter (x -> true)'와 동일합니다. 두 경우 모두 원래의 옵션을 되 돌리는 것입니다. – Holger

+3

가능한 경우,'helloWorldClientImplBean.allFamily()'를 수정하여 nullable'Family' 참조 대신'Optional '을 반환하는 것이 좋습니다.결과를'Optional.ofNullable'에 래핑하는 대신에,'Optional'의 생성을 피 호출자로 푸시하는 것이 좋을 것입니다. 여기에 표시된 호출 코드는'Optional'을 직접 사용하기 쉽습니다. 이것은 발신자가 널 체크를하거나 자신의'Optional' 포장을 강요하는 것이 바람직합니다. –

답변

6

오해의 주된 오해는 선택적으로 .filter(Objects::nonNull)과 같은 작업을 수행해야한다고 가정하는 것입니다. 빈 optional에 필터링이 필요한 경우, 옵션의 전체 목적을 무효화합니다. 특히, 술어가 false으로 평가 될 때 필터링의 결과가 다시 빈 선택 사항이되므로, 다시 정사각형 1로 되돌립니다.

사실, 은 비어 있지 않은 옵션의 경우 항상 true이고, 어쨌든 평가되지는 않는 빈 옵션의 경우 .filter(x -> true)과 동일한 효과가 있습니다.

또한 ifPresent에 대해 이미 알고 있음에도 불구하고 if 문으로 다시 전환됩니다.그래서 원래의 코드에서 파생 된 하나 똑바로 앞으로 솔루션은

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)을 스트림에 삽입하는 것보다

+4

"서비스 구현에 책임이있는 사람은 누구나 킥을." –

+0

@Hoger, 에 대해서는 동의하지 않는 것 같습니다. minOccurs = 0은 null이 될 수 있음을 이해합니다. 글쎄, 내 실제 작업에서, 나는 이것을 많이 본다. 정부 xsd에서 와 같은 경우를 볼 수 있으며 null 일 수도 있음을 알고 있습니다. –

+2

합리적인 API는 'null'대신 빈 목록을 반환해야합니다. 그러나 그것은 이미 내 대답의 첫 부분에서 다루어졌습니다. 내 대답의 두 번째 부분은 스트림 요소에 적용된'null' 체크를 가리키며, 목록이'null'이 아니고 비어 있지 않고'null' 요소를 포함하는 경우에만 필요합니다. 이것은 서비스 구현 자에 대한 저의 발언을 이끌어 낸 나쁜 범죄자입니다. * 그러한 시나리오가 실제로 가능하다면. 그렇지 않다면, 제 대답의 첫 번째 부분의 해결책은 이미 충분합니다. 'null'대신에 빈 콜렉션을 반환하는 것이 더 좋을지라도. – Holger

2

Optional으로 결과를 처리하려면 Holger's answer을 참조하십시오. 여기서 저는 다른 접근 방식을 취하고 싶습니다.

자신에게 하나의 질문을하십시오. 왜이 경우를 처리하려면 Optional이 필요할까요? 반환 된 Family 값에 if 블록이있는 Null 검사를 사용하지 않는 것이 맞습니까?

이 코드를 고려

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.flatMap을 통해 수행되는 스트림의 첫 번째 요소를 반환하는 작업을 제외한 Optional을 통해 Optional.map 작업을 통해 수행됩니다. 스트림은 단계별로 처리되며 1 회선으로 처리되지 않습니다. Optional에 대해 수행 된 모든 작업은 null 안전합니다. 즉, 반환 된 초기 Family 인스턴스가 null인지 여부뿐만 아니라 f.getChildren()null을 반환하는지 여부도 확인한다는 의미입니다.

어떤 버전이 더 짧습니까? 어느 것이 더 우아한가? 어느 것이 더 명확하고 이해하기 쉬우십니까? 어느 것이 가장 좋은 방법으로 프로그래머의 의도를 표현합니까?

내가 답을 알고 ...

+0

나는 Holger가 나의 질문에 완전히 대답했다는 것을 정직해야한다. 그러나 그와 당신 모두는 나의 학습 순간에 많은 것을 추가했다. 그러나, "왜이 케이스를 처리하기 위해 Optional이 필요할 것인가?"라는 질문을했기 때문에 또 다른 질문을 던지고 싶습니다 : 위의 케이스는 매우 간단하지만 더 복잡한 중첩 타입이 있습니까? 이제 정부 서비스를 호출 할 클라이언트를 코딩하고 있으며 회사의 모든 데이터가 단일 호출로 검색됩니다. 따라서 계층 트리가 훨씬 복잡해졌으며 선택 사항이이 경우 가치가 있는지 알아 내려고 노력하고 있습니다 –

+0

다른 두 가지 질문을 얼마나 복잡하게 생각하고 있는지 생각할 수 있습니다. https://stackoverflow.com/questions/47283660/is-it-possible-to-use-twice-ifpresent-each-one-checking-different-return-or-bifu 및 https://stackoverflow.com/questions/47414987/how-effciently-read-soap-web -service-response-with-nested-objects-using-streams. 첫째, 나는 당신과 홀거의 대답을 잘 이해해야하며, 안녕하세요 세계의 예에서 계층 구조를 높이기 위해 앞으로 나아가 야합니다. 그러면 "어느 것이 더 명확하고 이해하기 쉬울까요?"라고 대답 할 수있을 것입니다. –

+1

안녕하세요 @JimC! 정부 서비스에 대한 고객과의 행운을 빈다. (필자는 여러 번 그렇게해야했다. 나의 경우에는 아주 좋은 작업이 아니다.) 소비하는 서비스는 어렵고, 쉽고 간단하고 사용하기 쉬운 방법을 찾기 란 쉽지 않다. 귀하의 클라이언트에서 코드를 읽습니다. 즉, 특별한 경우, null 검사, 일관성 검사, 기본값 제공 등을 처리해야합니다. 모든 작은 가정과 반환 된 값에 대한 모든 규칙 또는 정의를 문서화해야합니다. 'if/for'와'Optional/Stream'을 비교할 때 아무런 간단한 대답이 없다는 것을 발견했습니다 ... –