스트림, 스트림 활용

2023. 2. 12. 14:25

스트림

  • 스트림이란 무엇인가?
  • 컬렉션과 스트림
  • 내부 반복과 외부 반복
  • 중간 연산과 최종 연산

스트림

  • 선언형으로 컬렉션 데이터를 처리할 수 있는 기술
  • 스트림을 이용하면 멀티스레드 코드를 구현하지 않아도 데이터를 투명하게 병렬로 처리가능
    • 투명하게의 의미는 무엇일까? -> 7장에 계속
// java 8 이전

void Test() {
    // 칼로리가 400 미만인 메뉴 filter
    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> lowCaloricDishesName = new ArrayList<>();
    for(Dish dish : lowCaloricDishes) {
        lowCaloricDishesName.add(dish.getName());
    }
}
// java 8

import static java.util.Comparator.comparing;
import static java.util.stream.Collectors.toList;

void Test() {
    List<String> lowCaloricDishesName =
        menu.paralleStream()
            .filter(d -> d.getCalories() < 400) // 칼로리 400 미만 메뉴 선정
            .sorted(comparing(Dishes::getCalories)) // 정렬
            .map(Dish::getName) // 이름 가져오기
            .collect(toList());
}

스트림의 특징

  • 선언형으로 코드 구현 가능
    • for문이나 if문 블록으로 제어하는 것이아닌, '찾아줘', '걸러줘' 식의 수행이 가능
  • filter, sorted, map 처럼 여러 빌딩 블록 연산을 연결하여 복잡한 데이터 처리 파이프라인 생성 가능
  • 병렬처리 가능 (성능이 좋아짐)

스트림 시작하기

스트림의 정의

  • 데이터 처리 연산을 지원하도록 소스에서 추출된 연속된 요소
    • 컬렉션과 마찬가지로 스트림은 특정 요소 형식으로 이루어진 연속된 값 집합의 인터페이스 제공
    • 파이프라이닝 : 대부분의 스트림 연산은 스트림 연산끼리 연결해서 거대한 커다란 파이프 라인을 구성
    • 내부반복 : 반복자를 이용해서 명시적으로 반복하는 컬렉션과 달리 내부 반복 지원
  • 연산
    • filter : 특정 요소 제외
    • map : 한 요소를 다른 요소로 변환하거나 정보 추출
    • limit : 스트림 크기 축소
    • collect : 스트림을 다른 형식으로 변환

스트림과 컬렉션

컬렉션과의 차이

  • 데이터를 언제 계산하느냐가 가장 큰 차이
    • 컬렉션은 현재 자료구조가 포함하는 모든 값을 메모리에 저장
    • 스트림은 이론적으로 요청할 떄만 요소를 계산하는 고정된 자료구조

딱 한번만 탐색할수 있다.

  • 반복자와 마찬가지로 스트림도 한 번만 탐색 가능

  • 탐색된 스트림의 요소는 소비됨(휘발성)

  • 재탐색하려면 새로운 스트림 생성해야함

List<String> title = Array.asList("Java8", "In", "Action");
Stream<String> s = title.stream();
s.forEach(System.out::println); // title의 각 단어 출력
s.forEach(System.out::println); // 스트림이 이미 소비되어서 출력 안됨

외부 반복과 내부 반복

  • 컬렉션 인터페이스를 사용하려면 사용자가 직접 요소를 반복해야함 (ex) for-each)
    • 외부 반복이라고 함
List<String> names = new ArrayList<>();
for(Dish dish: menu) { // 메뉴 리스트를 명시적으로 순차 반복
    names.add(dish.getName());
}
  • 스트림 라이브러리는 내부 반복
List<String> names = menu.stream().map(Dish::getName).collect(toList());

내부 반복의 장점

  • 작업을 투명하게 병렬로 처리 가능
  • 최적화된 다양한 순서로 처리 가능

스트림 연산

중간 연산

  • filter나 sorted같은 중간 연산은 다른 스트림을 반환

최종 연산

  • 최종 연산은 스트림 파이프라인에서 결과를 도출함
    • 스트림 이외의 결과과 반환되면 최종 연산이다.

스트림 활용

  • 필터링 슬라이싱, 매칭
  • 검색, 매칭, 리듀싱
  • 특정 범위의 숙자와 같은 숫자 스트림 사용하기
  • 다중 소스로부터 스트림 만들기
  • 무한 스트림

필터링

프레디케이트 활용

List<Dish> vegetarianMenu = menu.stream()
                                .filter(Dish::isVegetarian) // 채식 요리인지 확인하는 메소드 참조
                                .collect(toList());

고유 요소 필터링

// 고유 요소 = 중복된 값이 없는 요소
// distinct 활용

List<Integer> numbers = Arrays.asList(1,2,1,3,3,2,4);
numbers.stream()
       .filter(i -> i % 2 == 0)
       .distinct()
       .forEach(System.out::println);

스트림 슬라이싱

프레디케이트 활용

  • 기본형
// 320 칼로리 이하의 음식을 필터링 하는 경우
List<Dish> filteredMenu
    = specialMeun.stream()
                .filter(dish -> dish.getCalories() < 320)
                .collect(toList());
  • takewhile 활용
    • 정렬이 되어있다 가정했을때 칼로리 320 넘어가는 경우에 작업 중단해줌
List<Dish> slicedMenu1
    = specialMenu.steram()
                 .takeWhile(dish -> dish.getCalories() < 320)
                 .collect(toList());
  • dropwhile 활용
    • 나머지 요소 선택하는 방법
    • 320 칼로리보다 큰 요소를 검색하는 경우
    • takewhile과 정반대 작업
List<Dish> slicedMenu1
    = specialMenu.steram()
                 .dropWhile(dish -> dish.getCalories() < 320)
                 .collect(toList());
  • 스트림 축소
    • limit 사용
List<Dish> dishes
    = specialMeun.stream()
                .filter(dish -> dish.getCalories() > 300)
                .limit(3)
                .collect(toList());
  • 요소 건너뛰기
    • 처음 n개 요소를 제외한 스트림을 반환하는 skip 메소드 지원
List<Dish> dishes
    = specialMeun.stream()
                .filter(dish -> dish.getCalories() > 300)
                .skip(2)
                .collect(toList());

매핑

스트림의 각 요소에 함수 적용

  • 스트림은 함수를 인수로 받는 map 메소드 지원
  • 함수를 적용한 결과가 새로운 요소로 매핑
List<String> dishNames = menu.stream()
                            .map(Dish::getName)
                            .collect(toList());

스트림 평면화

  • 리스트에서 고유 문자로 이루어진 리스트를 반환해보자
    • ex) ["Hello", "World"] -> ["H", "e", "l", "o", "W", "r", "d"]
words.stream()
    .map(word -> word.split(""))
    .distinct()
    .collect(toList());
  • 그러나 이렇게 할 경우 Stream<String[]>이 반환됨
    • map으로 전달한 람다는 각 단어의 String[]을 반환하기 때문
  • 우리는 Stream<String>을 반환하고 싶음
    • flatmap을 사용해보자
List<String> uniqueCharacters =
    words.stream()
        .map(word -> word.split(""))
        .flatMap(Arrays::stream) // 생성된 스트림을 하나의 스트림으로 평면화
        .distint()
        .collect(toList());

검색과 매칭

  • 적어도 한 요소와 일치하는지 확인 (boolean)
    • anyMatch
if (menu.stream().anyMatch(Dish::isVegeterian)) {
    // 추후 작업 ...
}
  • 모든 요소와 일치하는지 확인
    • allmatch
boolean isHealthy = menu.stream()
                        .allMatch(dish -> dish.getCalories() < 1000);
  • 일치하는 요소가 없는지 확인
    • nonematch
boolean isHealthy = menu.stream()
                        .noneMatch(d -> d.getCalories() >= 1000);
  • 요소 검색
    • findAny
    • 찾는 즉시 종료됨
    • findFirst 사용하면 첫번째 요소 반환(논리적인 아이템 순서에서) -> 병렬실행시 필요하다
Optional<Dish> dish =
        menu.stream()
            .filter(Dish::isVegeterian)
            .findAny();

리듀싱

  • 리듀싱 연산이란?
    • 모든 스트림 요소를 처리해서 값으로 도출하는 연산
    • 폴드라고도 한다
  • 요소의 합, 최댓값, 최솟값
// for-each 사용
int sum = 0;
for (int x : numbers) {
    sum += x;
}

// reduce 사용
int sum = numbers.stream().reduce(0, (a,b) -> a+b);  //0은 초기값


// 초기값이 없는 경우
Optional<Integer> sum = numbers.stream().reduce((a,b) -> (a+b));


// 최댓값
Optional<Integer> max = numbers.stream().reduce(Integer::max);

// 최솟값
Optional<Integer> min = numbers.stream().reduce(Integer::min);
  • reduce 메소드의 장점
    • 병렬화 가능

' > 모던 자바 인 액션' 카테고리의 다른 글

동적 파라미터화 코드 전달하기  (0) 2022.12.05

+ Recent posts