[모던 자바 인 액션] - 스트림 소개

김성혁·2022년 3월 14일
0

모던 자바 인 액션

목록 보기
4/7
post-thumbnail

👨🏻‍💻 스트림이란?

  • 자바 8 API에 새로 추가된 기능입니다.
  • 선언형(즉, 데이터를 처리하는 임시 구현 코드 대신 질의로 표현할 수 있다)으로 컬렉션 데이터를 처리할 수 있습니다.
  • filter, sorted, map, collect 같은 여러 빌딩 블록 연산을 연결해서 복잡한 데이터 처리 파이프라인을 만들어도 여전히 가독성과 명확성이 유지됩니다.
  • 멀티스레드 코드를 구현하지 않아도 데이터를 투명하게 병렬로 처리할 수 있습니다. 데이터 처리 과정을 병렬화하면서 스레드와 락을 걱정할 필요가 없습니다.

저칼로리의 요리명을 반환하고, 칼로리를 기준으로 요리를 정렬하는 자바 7 코드

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(dish.getCalories(), dish2.getCalories());
	}
});

List<String> lowCaloricDishesName = new ArrayList<>();

for(Dish dish: lowCaloricDishes) {
		lowCaloricDishesName.add(dish.getName());
}

위 코드는 lowCaloricDishes라는 '가비지 변수'를 사용했습니다. 즉, lowCaloricDishes는 컨테이너 역할만 하는 중간 변수입니다. 자바 8에서는 이러한 세부 구현은 라이브러리 내에서 모두 처리합니다.

자바 8

List<String> lowCaloricDishesName = menu.stream()
										.filter(d -> d.getCalories() < 400)
										.sorted(comparing(Dish::getCalories))										
                                        .map(Dish::getName)
                                        .collect(toList());

// parallelStream()을 이용하여 멀티코어 아키텍처에서 병렬로 실행
List<String> lowCaloricDishesName = menu.parallelStream()
										.filter(d -> d.getCalories() < 400)
										.sorted(comparing(Dish::getCalories))
										.map(Dish::getName)
										.collect(toList());

스트림의 정의

  • 데이터 처리 연산을 지원하도록 소스에서 추출된 연속된 요소(Sequence of element)
    • 컬렉션의 주제는 데이터고 스트림의 주제는 계산
    • 정렬된 컬렉션으로 스트림을 생성하면 정렬이 그대로 유지됩니다.
    • 파이프라이닝: 대부분의 스트림 연산은 스트림 연산끼리 연결해서 커다란 파이프라인을 구성할 수 있도록 스트림 자신을 반환
    • 내부 반복

스트림과 컬렉션

  • 둘의 가장 큰 차이는 데이터를 언제 계산하느냐 입니다.
  • 컬렉션의 모든 요소는 컬렉션에 추가되기 전에 계산되어야 합니다.
  • 스트림은 요청할 때만 요소를 계산하는 고정된 자료구조입니다. 즉, 사용자가 요청하는 값만 스트림에서 추출한다는 것이 핵심입니다.
  • 즉 생성과 소비 이 두 가지 측면을 생각한다면 컬렉션은 생산자 중심, 스트림은 생산자와 소비자 관계 형성 입니다.
  • 스트림은 단 한 번만 소비할 수 있다는 점을 명시하자!
  • 외부 반복과 내부 반복
    • 컬렉션 인터페이스를 사용하려면 사용자가 직접 요소를 반복해야 합니다.(외부 반복)
    • 스트림 라이브러리는 (반복을 알아서 처리하고 결과 스트림값을 어딘가에 저장해주는) 내부 반복을 사용합니다.

내부 반복이 외부 반복보다 더 좋은 이유

  • 작업을 투명하게 병렬로 처리하거나 더 최적화된 다양한 순서로 처리할 수 있습니다.
  • 스트림 라이브러리의 내부 반복은 데이터 표현과 하드웨어를 활용한 병렬성 구현을 자동으로 선택합니다.

👨🏻‍💻 스트림 연산

  • 중간 연산: 연결할 수 있는 스트림 연산
  • 최종 연산: 스트림을 닫는 연산

중간 연산

  • 중간 연산은 다른 스트림을 반환합니다.
  • 중간 연산의 특징은 게으르다(lazy)는 것입니다.
    • 단말 연산을 스트림 파이프라인에 실행하기 전까지는 아무 연산도 수행하지 않는다는 것입니다.

      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

최종 연산

  • 최종 연산은 스트림 파이프라인에서 결과를 도출합니다.

0개의 댓글