지난 포스트에 이어서, 계속 Stream API에 대한 포스팅을 하고 있습니다.


지난 포스팅에서는 각 컨테이너를 매핑하여, 원하는 형태의 객체로 출력을 할 수 있는 방법을 소개했었습니다. 

마치 SQL 처럼 특정 필드만을 추출한다던가, 두 개 이상의 컨테이너를 JOIN 할 수도 있었죠.


해당 포스팅은 이 곳에서 확인!! 



오늘은 특정 속성이 데이터 집합에 있는지, 있다면 해당 객체를 추출할 수 있는 다양한 유틸 메소드를 살펴볼까 합니다.


예를 들어, 우리는 이러한 작업을 많이 하곤 합니다.


아래 로은 특정 숫자 컬렉션에서 5의 배수가 있는지 확인하는 로직입니다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
List<Integer> sampleList = Arrays.asList(5,10,12,38);
        
Boolean isHave = false;
for (Integer number : sampleList) {
    if (number % 5 == 0) {        
        // 5의 배수인 데이터가 있는지 확인
        isHave = true;
        break;
    }
}
        
System.out.println("5의 배수가 존재  : " + isHave);
 
// 출력 : 5의 배수가 존재  : true
cs


루프를 돌면서, 출력할 컬렉션 객체에 if 조건을 사용하여 여부를 업데이트하려 하고 있습니다.


해당 로직은 다음과 같이 수정될 수 있습니다.



1. Predicate 가 적어도 한 요소와 일치하는지 확인 (anyMatch)


anyMatch 유틸 메소드로 다음과 같은 로직을 구할 수 있습니다.


1
2
3
4
5
List<Integer> sampleList = Arrays.asList(5,10,12,38);
        
System.out.println("5의 배수가 존재  : " + sampleList.stream().anyMatch(n -> n % 5 == 0));
 
// 출력 : 5의 배수가 존재  : true
cs


단순히 predicate 형태로 컬렉션 제네릭 객체의 조건만 명시해주면 조금 더 간소화된 코드를 구현할 수 있습니다.


2. Predicate 를 이용한 기타 확인 (allMatch, noneMatch)


Predicate 를 이용하여 컬렉션의 모든 요소가 일치하는지를 확인하려면, allMatch 메소드를 사용할 수 있습니다. 또한 반대로 일치하는 요소가 없는지를 확인하는 기능 역시도 noneMatch 메소드를 사용함으로써 간단하게 구현할 수 있습니다.


1
2
3
4
5
6
7
8
9
List<Integer> sampleList = Arrays.asList(5,10,15,40);
        
System.out.println("모든 요소가 5의 배수인가? : " + sampleList.stream().allMatch(n -> n%5 == 0));
System.out.println("5의 배수는 없는가? : " + sampleList.stream().noneMatch(n -> n % 5 == 0));
 
// 출력
// 모든 요소가 5의 배수인가? : true
// 5의 배수는 없는가? : false
 
cs


3. 요소 검색


Stream API 를 사용하면 쉽게 한 요소에 접근할 수 있습니다. 일련의 중간 연산(filter, distinct 등)을 수행한 후 findAny, findFirst 등의 최종연산 메소드로 조건에 맞는 한 요소를 선택할 수 있습니다. 예제를 한번 볼까요? 


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
List<Integer> sampleList = Arrays.asList(17,19,28,37,16);
        
sampleList.
    stream().
    filter(n -> n % 2 == 0).
    findAny().
    ifPresent(n -> System.out.println("2의 배소의 요소 찾기 : " + n));
        
sampleList.
    stream().
    filter(n -> n % 2 == 0).
    findFirst().
    ifPresent(n -> System.out.println("2의 배소의 첫번째 요소 찾기 : " + n));
 
// 출력
// 2의 배소의 요소 찾기 : 28
// 2의 배소의 첫번째 요소 찾기 : 28
cs


두 메소드의 내용을 살펴보면, 동일함을 알 수 있습니다. 왜냐하면, 쇼트서킷 검사(보통 Boolean 연산 특정 조건에 의해 검사결과가 정해지면 추 후 연산은 안하는 행위)를 통해 결과를 찾은 뒤 즉시 실행을 하기 때문이죠.


그렇다면 왜 굳이 메소드를 두 개로 둔 이유가 무엇일까요?


그 이유는 공짜로 얻을 수 있는 병렬성 이 후 후처리 때문입니다. Collection 클래스에서 Stream 추출 시, parallelStream 을 통해 병렬 처리를 쉽게 수행할 수 있습니다. 그러나 병렬처리를 수행 시 컬렉션의 순서가 보장이 되지 않습니다! ㅡㅡ^


즉 병렬처리를 통해 데이터 연산을 수행하였다면, 명시적으로 첫 번째 요소를 찾기 위해 findFirst 를 사용하여야만 합니다! 


아래 예제를 통해 결과를 확인해볼까요? 아래 예제는 병렬처리를 했음에도 findFirst 사용 시, 첫 번째 요소찾기를 보장하고 있음을 보여주고 있습니다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
List<Integer> sampleList = Arrays.asList(17,19,28,37,16);
        
sampleList.
    parallelStream().
    filter(n -> n % 2 == 0).
    findAny().
    ifPresent(n -> System.out.println("2의 배소의 요소 찾기 : " + n));
        
sampleList.
    parallelStream().
    filter(n -> n % 2 == 0).
    findFirst().
    ifPresent(n -> System.out.println("2의 배소의 첫번째 요소 찾기 : " + n));
 
// 출력
// 2의 배소의 요소 찾기 : 16
// 2의 배소의 첫번째 요소 찾기 : 28

cs



마지막으로 생각해봐야할 부분은 요소 검사 메소드 활용 시, 결과가 항상 있다는 것을 보장하지 않는다는 점입니다. 보통 이럴 때 기존 자바에서는 null 로써, 데이터를 처리했지만 JAVA8 부터 많은 함수형 프로그래밍에서 볼 수 있는 Optional 의 개념이 생겼습니다. 


Optional값의 존재나 부재 여부를 표현하는 컨테이너 클래스로, 예제에서 사용한 ifPresent 는 값이 존재할 때만 실행을 명령하는 메소드 입니다. (Optional 은 추 후 따로 포스팅을 한 후 연결을 시켜두겠습니다. ㅡㅡ^)



자바 8 인 액션
국내도서
저자 : 라울-게이브리얼 우르마(RAOUL-GABRIEL URMA),마리오 푸스코(MARIO FUSCO),앨런 마이크로프트(ALAN MYCROFT) / 우정은역
출판 : 한빛미디어 2015.04.01
상세보기



반응형
Posted by N'