☕ Java

모던 자바 인 액션 - Stream(스트림)이란?, 스트림특징, 내부반복/외부반복, 게으른중간연산

연_우리 2022. 7. 11. 22:01
반응형

목차

     

     

     

    Stream 이란?

    SELECT name FROM dishes WHERE calorie < 400

     

    위 SQL문은 칼로리가 낮은 요리명을 선택하라는 질의이다.

    SQL에서는 요리의 속성(칼로리)를 이용하여 어떻게 필터링할 것인지 개발자가 직접 구현하지 않아도 된다.

    하지만 자바에서 필터링하려면 반복문을 돌려서 처리해야한다.

     

    요소가 많은 컬렉션이라면 어떻게할까??

    똑같이 반복문으로 돌려서 처리하면 굉장히 많은 시간이 필요할 것이고,

    작업을 병렬로 처리해야 성능을 높일 수 있을 것이다.

     

    저칼로리로 요리를 필터링하고, 칼로리 순서대로 요리를 정렬하는 코드를 작성해보자.

    //1. 저칼로리 요리 필터링
    //2. 칼로리로 요리 정렬
    ---------------------------------------------
    //반복자 이용
    List<Dish> lowCaloricDishes = new ArrayList<>();
    for(Dish dish: menu){
    	if(dish.getCalories() < 400){
        	lowCaloricDishes.add(dish);
        }
    }
    
    Collections.sort(lowCaloricDishes, new Comparator<Dish>() {
    	public int compare(Dish dish1, Dish dish2){
        	return Integer.compare(dish1.getCalories(), dish2.getCalories());
        }
    });
    ----------------------------------------------
    //스트림 이용
    List<String> lowCaloricDishes = menu.stream()
    	.filter(dish -> dish.getCalories() < 400)
        .sorted(comparing(Dish::getCalories))
        .collect(toList());

    자바8 이전에 병렬처리는 복잡하고 어려웠는데, 이것을 편안하게 처리해주는 것이 Stream(스트림)이다.

     

    스트림을 이용하면 마치 SQL처럼 컬렉션 데이터를 쉽게 처리할 수 있고, 손쉽게 병렬처리가 가능해진다.

    (위 코드에서도 stream()을 parallelStream()으로 바꾸면 병렬처리가 가능해진다!)

     

     

    모던액션 인 자바에서 가져옴

     

    스트림의 특징

    스트림은 다양한 데이터 처리연산을 지원하는 연속된 요소이다.

    그렇기때문에, 스트림 연산은 연산(filter, sorted, map...)끼리 연결해서 데이터처리 파이프라인을 구성할 수 있다.

    (파이프라인은 데이터처리에 적용하는 질의같은 존재이다.)

     

    스트림은 자바 8 이전과는 다르게, 어려웠던 병렬처리를 손쉽게 사용할 수 있게 스레드와 락을 알아서 지정해준다.

     

    스트림은 for문과 같은 명시적 반복이 아닌, 내부반복을 지원한다.

     

     

     

    컬렉션과 스트림

    스트림과 컬렉션은 모두 순차적으로 값에 접근한다는 공통점이 있다.

     

    컬렉션은

    모든 요소를 메모리에 저장하는 자료구조이다.

    즉, 컬렉션에서 값을 추출하려면 컬렉션의 모든 요소를 알고있어야한다.

    EX. DVD에 저장된 영화. 어느 시점을 찍어도 곧바로 재생된다. 모든 값을 이미 알고있기때문!

     

    스트림은

    이론적으로 요청할때만 요소를 계산하는 고정된 자료구조이다.

    스트림은 생산자와 소비자 관계를 형성하는데, 사용자가 데이터를 요청할때만 값을 계산한다.

    즉, 게으르게 만들어지는 컬렉션과 같다.

    EX. 스트리밍하는 영화. 어느 시점을 찍으면 로딩 후 재생된다. 사용자가 요청할때만 값을 계산하기때문!

     

     

     

    스트림의 소비성

    for문을 생각해보자. i=0; i<5; i++ 이라면 i=1인 경우는 몇번 탐색되는가?? 1번이다.

    동일하게, 스트림도 1번만 탐색할 수 있다.

     

    차이점이 있다면 스트림의 요소는 소비되기때문에, 

    다시 탐색하려면 초기 데이터 소스에서 새로운 스트림을 만들어야한다.

     

     

     

    내부반복과 외부반복

     

    컬렉션의 요소를 반복문으로 직접 탐색하는 것을 외부반복이라한다.

    List<String> names = new ArrayList<>();
    
    for(Dish dish: menu){
    	names.add(dish.getName());
    }
    //메뉴 리스트를 명시적으로 순차 반복한다.

     

    외부반복은 아래 대화와 동일하게 이루어진다. 명시적으로 컬렉션 요소를 하나씩 가져와 처리한다.

     

    반면 스트림은 반복을 알아서 처리하고, 결과를 어딘가에 저장해주는 내부반복을 사용한다.

    스트림의 내부반복은 람다 표현식을 인수로 받기때문에, 어떤 작업을 수행할지만 지정하면 모든 것이 알아서 처리된다.

    List<String> names = menu.stream()
    	.map(Dish::getName)
    	.collect(toList());
        
    //메뉴리스트를 내부적으로 순차 반복한다

     

    스트림의 내부반복은 위의 마리오와 소피아와의 대화에서

    소피아에게 "바닥에 있는 모든 장난감을 상자에 담자" 라고 말하는 것과 같다.

     

    내부반복의 장점 첫번째는

    소피아가 한손에는 인형, 한손에는 공을 동시에 들 수 있다는 점이다. (병렬성)

     

    장점 두번째는 모든 장난감을 상자가까이 이동시킨 다음에 장난감을 상자에 넣을 수 있다는 점이다.(최적화)

     

     

    이렇듯 스트림의 내부반복은 작업을 투명하게 병렬로 처리하거나, 더 최적화된 다양한 순서로 처리할 수 있다.

    또한 하드웨어를 이용한 병렬성 구현을 자동으로 선택하기 때문에 손쉽게 병렬성을 얻을 수 있다.

     

     

     

    스트림 연산

    스트림의 연산은 크게 두가지로 구분할 수 있다.

    중간연산과 최종연산이다.

     

    중간연산은

    연속해서 스트림연산이 가능하도록 스트림을 반환하고, 

     

    최종연산은

    스트림연산을 닫고 다른값으로 반환하도록 한다.

     

     

    게으른 중간연산

    List<String> names = menu.stream()
    	.filter(dish -> {
        	System.out.println("filtering : " + dish.getName());
            return dish.getCalories() > 300;
        })
        .map(dish -> {
        	System.out.println("mapping : " + dish.getName());
            return dish.getName();
        })
        .limit(3)
        .collect(toList());
    -----------------------------------------------
    실행결과
    
    filtering: pork
    mapping: pork
    filtering: beef
    mapping: beef
    filtering: chicken
    mapping:chicken

    중간연산은 단일연산을 최종연산 전에 수행하지 않는다. = 게으르다!

    중간연산을 모두 합친다음 최종연산에서 한번에 처리하기 때문에

    위 예제에서도 filtering이 이어서 나오지 않고 mapping과 번갈아 가며 나온것이다.

     

    다시말해, 중간연산을 이용해서 파이프라인을 구성할 수 있지만

    중간연산으로는 어떤 결과도 생성할 수 없다!

     

     

     

     

     

     

     

     

    #스트림 #Stream #자바 #자바8 #모던액션인자바 #스트림특징 #Stream특징 #스트림의소비성 #스트림의내부반복 #스트림의외부반복 #스트림중간연산 #스트림최종연산 #게으른중간연산 #중간연산 #최종연산 #내부반복 #외부반복

    반응형
    • 네이버 블러그 공유하기
    • 페이스북 공유하기
    • 트위터 공유하기
    • 구글 플러스 공유하기
    • 카카오톡 공유하기