지금까지는 Stream API 의 최종 연산에서 여러 종류의 값 (Boolean, filter 된 리스트, void 연산) 을 반환을 함을 알 수 있었습니다. 여러 중간 연산을 pipe 처럼 묶어서 사용하고, 그 결과를 취함으로 여러 로직의 이점을 많이 챙길 수 있었습니다.


오늘의 포스팅 주제는 조금 더 복잡한 질의를 표현할 수 있는 리듀싱(reducing)에 대해 알아보려고 합니다. 예를 들어, 컬렉션 내부에 대해 총합을 구한다거나 최대값, 최소값을 구하는 연산이 대표적이라고 할 수 있습니다. ㅡㅡ^


 간단한 예제 부터 한번 봐보도록 할까요? 아래 코드는 컬렉션 내부의 값 중, 짝수 만을 덧셈하는 연산입니다.


1
2
3
4
5
6
7
8
9
10
11
12
13
List<Integer> numberList = Arrays.asList(5103692);
        
int sum = 0;
        
for (Integer number : numberList) {
    if (number % 2 == 0) {
        sum += number;
    }
}
 
System.out.println("총 합 : " + sum);
 
// 총 합 : 108
cs


 이 간단한 코드는 Stream 연산을 통해 더욱 간단해질 수 있습니다. 약간의 중간연산과 리듀싱을 통해 처리할 수 있습니다.


1
2
3
4
5
6
7
8
9
10
List<Integer> numberList = Arrays.asList(5103692);
 
System.out.println("총 합 : " + 
            numberList.
                stream().
                filter(n -> n % 2 == 0).
                reduce(0, (a, b) -> a + b)
            );
 
//총 합 : 108
cs


 filter 를 통해 짝수만 추출했으며, reduce 를 이용해 덧셈의 결과를 구했습니다.


위의 예제에서 사용한 reduce 는 초기 값을 0 을 했음을 알 수 있습니다. 물론, 초기값이 없는 연산을 수행할 수 있습니다. 이 때 결과는 Optional 객체로 나타나게 되며 해당 객체를 이용해 값이 있을 때만 처리하는 기능 등을 수행할 수 있습니다.


리듀싱의 응용은 다양하게 할 수 있습니다. 예를들어 특정 객체에 대한 컬렉션 중, 한 필드에 대한 합은 다음과 같이 구할 수 있습니다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
/**
 * Test 를 위한  VO
 * 
 * @author namduhyeon
 *
 */
class TestVo {
    private String name;
    private Integer value;
    
    public TestVo (String name, Integer value) {
        this.name = name;
        this.value = value;
    }
 
    public String getName() {
        return name;
    }
 
    public Integer getValue() {
        return value;
    }
}
 
List<TestVo> numberList = Arrays.asList(
                new TestVo("Doohyun Nam",7)
                , new TestVo("Ford Mill",9)
                , new TestVo("Hwang",2));
        
numberList.stream().
    map(v -> v.getValue()).
    reduce((v1, v2) -> v1 + v2).
    ifPresent(System.out::println);
 
// 18
cs


특정 필드를 추출하기 위해 map 을 사용하였으며, 이 Stream 을 통해 합을 구할 수 있었습니다. 


이러한 map 과 reduce 를 이용하여, 데이터를 가공하는 패턴을 map-reduce 패턴이라 하며 웹 검색 엔진 등에서 많이 사용하는 것을 볼 수 있습니다.


그 이유는 언제나 Stream API 에서 쉽게 볼 수 있는 병렬성이라는 특징 덕분입니다. 예를 들어 특정 컬렉션의 합을 병렬적으로 구하기 위해서는 임계영역간 스레드의 경쟁 비용이 발생하기 마련이지만, 리듀스 내부의 내부반복은 개발자에게는 추상화되어 제공됩니다. (분할하여 결과를 구하고 머징하는 과정이 아닐지...) 


하지만, 모든 기능이 만병통치약일 수는 없습니다. ㅡㅡ^ 

병렬스트림으로 리듀싱 결과를 일반 스트림과 동일하게 얻기 위해서는 연산이 어떤 순서로 연산되어도 결과가 보장되는 구조이며, 람다 내부 인스턴스는 변경되어서는 안되는 구조이어야 합니다. 즉 각 트랜잭션 중 병렬구조로 처리가 가능한 부분만 사용이 가능함을 알 수 있습니다.


복잡한 데이터의 질의를 쉽게 표현할 수 있는 리듀싱에 대해 알아보았습니다. 생각보다 자주 접해보면, 금방 눈에 들어오지 않을까 생각이 듭니다. 


점점 함수형 프로그래밍의 장점이 눈에 들어오지 않으시나요? :-) 


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




반응형
Posted by N'