JAVA8 이 기존 JAVA와 달라진 부분은 단지 람다표현식이 추가된 것만 있는 것은 아닌 것 같아요. 


람다 표현식과 더불어 같이 추가된 것은 컨테이너(Collection)를 사용하는 비지니스 로직을 보다 간결하고(쉽지는 않을듯? 배워야하기 때문이죠.ㅡㅡ^ ) 성능 좋게 만들 수 있는 Stream API의 등장은 clean code 제작에 많은 도움이 될 것같습니다.


요즘 개발하고 있는 Spring 기반의 back단 작업이나 안드로이드위에서 작성하는 비지니스 로직은 DB를 껴안고 있기 때문에 컨테이너를 자주 사용하게 되고, 당연히 루프를 돌면서 이 작업, 저 작업을 하게 됩니다.


비지니스 로직에 따라 if, break, continue 등 여러 문법 등을 사용하게 되고, 때에 따라 복잡해지는 것을 작성해줘야하지만 Stream API를 사용하면 내가 무슨일을 해야한다는 것을 어떤 규칙에 따라 선언만 해주면 되는 선언형 프로그래밍을 할 수 있습니다. (SQL 질의 처럼 말이죠. SQL 질의어를 사용하면 복잡한 key-value 기반의 테이블에서 효과적으로 데이터를 찾을 수 있죠. 찾는 로직은 몰라도 되고요.)


예를 들면 다음과 같은 코드를 볼 수 있습니다. dataList 라는 컨테이너를 이용해서 같은 로직을 작성해보겠습니다.


1
2
3
4
5
6
7
8
LinkedList<Integer> dataList = new LinkedList<Integer>() {
    {
        Random random = new Random();
        for (int i = 0; i < 100++i) {
            add(random.nextInt(100));
        }
    }
};
cs


아래는 기존 JAVA8 이전 Collection을 loop돌면서 컨테이너 내부의 가장 큰 7의 배수 4개를 찾는 것입니다. (아무리 라인수를 줄일려고 해도 저는 이정도가 한계인가 봅니다. ㅜㅡㅜ)


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
Collections.sort(dataList, new Comparator<Integer>() {
    @Override
    public int compare(Integer arg0, Integer arg1) {
        // TODO Auto-generated method stub
        return arg1.compareTo(arg0);
    }
});
 
for (int i = 0, count = 0, size = dataList.size(); i < size && count < 4++i) {
    if (dataList.get(i) % 7 == 0) {
        System.out.println(dataList.get(i));
        ++count;
    }
}
 
cs


다음은 Stream API를 사용한 로직입니다.


1
2
dataList.stream().filter((a) -> a % 7 == 0).sorted((a, b) -> b.compareTo(a)).limit(4)
                .forEach(System.out::println);
cs


stream 내부 메소드를 이용해 질의를 하듯이 코드를 작성하였습니다. 정말 간단해졌죠?


이와같이 Stream API를 이용하면 단순히 비지니스 처리를 선언으로 하기 때문에 쉽게 구현할 수 있으며, 각각의 블럭 (sorted나 filter) 등이 chain 형식을 지원하기 때문에 복잡한 로직에 대해 가독성과 명확성을 확실시 해줄 수 있을 것같습니다. (즉 파이프라인 구조입니다.)


또한 내부적으로는 데이터 처리과정을 병렬화할 수 있기 때문에 성능을 더 좋게 만들 수 있습니다. 

(물론 병렬을 위한 thread 처리에 대해서는 신경쓰지 않아도 됩니다.)


이와같은 Stream API의 특징을 JAVA8 IN ACTION에서는 다음과 같이 요약하고 있습니다.


1. 선언형

더 간결하고 가독성이 좋아집니다.


2. 조립할 수 있음

유연성이 좋아집니다.


3. 병렬화

성능이 좋아집니다.


그럼 여기에서, Stream의 구조에 대해서 조금 더 살펴보죠.

Stream 을 사용하는 구조는 크게 데이터 소스를 받는 것과 비지니스 로직에 대한 각종 컨디션을 파이프라인으로 구성하기 위한 중간 연산, 구성할 파이프라인을 실행할 최종 연산정도 있습니다.


위의 작성한 코드를 살펴보면 다음과 같이 나타낼 수 있네요.


1
2
3
4
dataList.stream()        // 데이터 소스 
.filter((a) -> a % 7 == 0).sorted((a, b) -> b.compareTo(a)).limit(4)  // 중간 연산
.forEach(System.out::println);    // 최종 연산
 
cs


위의 역할을 살펴보면, dataList 로 부터 데이터 소스를 받아오고 있으며, 7의 배수, 내림차순, 선착순 4개 등 chain 형식으로 각종 컨디션을 연결한 중간 연산과정이 있으며, 이러한 조건의 값들을 console에 표시하겠다는 최종 연산으로 나누어짐을 알 수 있습니다. 

(Stream API에서 제공하는 보다 더 다양한 연산은 다음에 정리하여, 포스팅하도록 하겠습니다. )


이러한 Stream API를 이용한 스트림 요소의 연산은 오직 딱 한번만 사용(소비)할 수 있습니다. 예를들어 이 것은 예외를 일으킵니다.


1
2
3
4
5
6
7
Stream<Integer> stream = dataList.stream();
 
stream.filter((a) -> a % 7 == 0).sorted((a, b) -> b.compareTo(a)).limit(4)
        .forEach(System.out::println);    
stream.filter((a) -> a % 7 == 0).sorted((a, b) -> a.compareTo(b)).limit(10)
        .forEach(System.out::println);        // IllegalStateException 예외 발생
 
cs


아래는 예외 메시지입니다.


1
2
3
4
5
6
7
8
Exception in thread "main" java.lang.IllegalStateException: stream has already been operated upon or closed
    at java.util.stream.AbstractPipeline.<init>(Unknown Source)
    at java.util.stream.ReferencePipeline.<init>(Unknown Source)
    at java.util.stream.ReferencePipeline$StatelessOp.<init>(Unknown Source)
    at java.util.stream.ReferencePipeline$2.<init>(Unknown Source)
    at java.util.stream.ReferencePipeline.filter(Unknown Source)
    at testClass.main(testClass.java:40)
 
cs


이것으로 Stream API 에 대한 Summary 정도는 여기까지 포스팅하겠습니당. 

(나머진 필요한 것 따라 메소드를 사용해주면 되지 않을까요? 그래서 Summary 입니다. ㅡㅡ^)



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




반응형

'개발이야기 > 함수형 프로그래밍' 카테고리의 다른 글

Stream API 활용편1 (필터와 슬라이싱)  (0) 2016.08.26
Stream과 Collection 차이  (0) 2016.08.04
람다 조합  (0) 2016.08.02
메서드 레퍼런스  (0) 2016.08.01
람다의 실제 형식  (0) 2016.07.28
Posted by N'