Stream API 활용편3 (검색과 매칭)
지난 포스트에 이어서, 계속 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 |
마지막으로 생각해봐야할 부분은 요소 검사 메소드 활용 시, 결과가 항상 있다는 것을 보장하지 않는다는 점입니다. 보통 이럴 때 기존 자바에서는 null 로써, 데이터를 처리했지만 JAVA8 부터 많은 함수형 프로그래밍에서 볼 수 있는 Optional 의 개념이 생겼습니다.
Optional 은 값의 존재나 부재 여부를 표현하는 컨테이너 클래스로, 예제에서 사용한 ifPresent 는 값이 존재할 때만 실행을 명령하는 메소드 입니다. (Optional 은 추 후 따로 포스팅을 한 후 연결을 시켜두겠습니다. ㅡㅡ^)
|
'개발이야기 > 함수형 프로그래밍' 카테고리의 다른 글
Stream API 활용편5 (숫자형 스트림, 스트림 생산) (0) | 2017.01.25 |
---|---|
Stream API 활용편4 (리듀싱) (0) | 2017.01.23 |
Stream API 활용편2 (매핑) (0) | 2016.08.27 |
Stream API 활용편1 (필터와 슬라이싱) (0) | 2016.08.26 |
Stream과 Collection 차이 (0) | 2016.08.04 |