스트림 예시

  • filter : 필터링
  • map : 추출
  • limit : 스트림 크기 축소
    List<String> threeHighCaloriesDishNames = dishes.stream()
    .filter(dish -> dish.getCalories() > 300)
    .map(Dish::getName)
    .limit(3)
    .collect(toList());
    System.out.println(threeHighCaloriesDishNames);
    

스트림의 특징

파이프라이닝

  • 스트림 연산은 파이프라인으로 연결되어 있어, 한 연산의 결과가 다음 연산의 입력으로 이어진다. 덕분에 laziness, short-circuiting을 할 수 있다.

스트림은 내부 반복을 사용한다.

  • 반복자를 이용해서 명시적인 반복을 하는 컬렉션과 달리 스트림은 내부 반복을 합니다.

내부반복과 외부반복의 차이점

  • 내부반복 : 라이브러리 내부에서 모든 요소를 반복하며, 개발자는 요소에 대한 처리만을 제공한다. (stream)
  • 외부반복 : 개발자가 직접 요소를 반복하며, 개발자가 요소에 대한 처리와 반복을 모두 제어한다. (for-each)
  • 아래의 그림은 외부반복입니다. img_1.png
  • 내부반복이 왜 좋은가?
    • 병렬성 : 내부 반복을 사용하면 병렬성을 쉽게 활용할 수 있다. 즉, 소피아는 한 손에 인형을 또 다른 손에는 공을 동시에 들 수 있게 된다.
    • 기능에 집중 : 스트림 라이브러리가 요소 반복을 처리하기 때문에 개발자는 요소에 대한 처리에만 집중할 수 있다.

딱 한번만 탐색 할 수 있다.

  • 한번 탐색된 스트림은 소비됩니다. 따라서 스트림을 재사용하려면 새로운 스트림을 생성해야 합니다.
    Stream<Dish> stream = dishes.stream();
    stream.forEach(System.out::println);
    stream.forEach(System.out::println); // java.lang.IllegalStateException: stream has already been operated upon or closed
    

컬렉션과 스트림의 차이점

컬렉션도 스트림도 데이터의 연속적인 형태입니다. 둘의 큰 차이점은 데이터를 언제 계산하는가입니다.

  • 컬렉션 : 모든 데이터를 메모리에 저장하는 자료구조
  • 스트림 : 요청할 때만 요소를 계산하는 고정된(스트림에 요소를 추가하거나 제거할 수 없다) 자료구조

⭐중간연산⭐

  • filter, map, limit, sorted, distinct 등이 있다.
  • 중간연산은 다른 스트림을 반환한다. (즉, 파이프라인을 구성할 수 있다.)

중간연산 - Lazy Evaluation

  • 종단 연산을 실행하기 전까지는 중간 연산을 통해 설정된 스트림 파이프라인은 아무런 데이터 처리를 하지 않습니다.
    즉, 스트림에 데이터가 흐르고 모든 중간 연산이 적용되기 위해서는 반드시 종단 연산이 호출되어야 합니다.
    종단 연산이 호출될 때 모든 중간 연산들이 실행되고, 그 결과가 최종적으로 생성되거나, 소비되거나, 축소됩니다.

중간연산 - Short-circuiting & Loop Fusion

  • Short-circuiting
    • limit, findFirst, anyMatch, allMatch, noneMatch 등이 있다.
    • 스트림 파이프라인을 탐색하는 도중에 원하는 결과가 나오면 즉시 탐색을 중단하고 결과를 반환한다.
    • 즉, 모든 요소를 탐색하지 않아도 원하는 결과를 얻을 수 있다.
  • Loop Fusion : 스트림 파이프라인을 한번만 탐색하는 것이 아니라, 여러 연산을 하나로 합쳐서 한번만 탐색하는 것을 말한다.
    private static void example6_loop_fusion() {
      List<String> collect = dishes.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());
    
      System.out.println(collect);
    
      /**
       * filtering 피자
       * mapping 피자
       * filtering 햄버거
       * mapping 햄버거
       * filtering 샐러드
       * mapping 샐러드
       * [피자, 햄버거, 샐러드]
       * */
    }
    

최종연산

  • collect, forEach, count, reduce 등이 있다.
  • 최종연산은 스트림을 닫는다. (즉, 스트림을 소비한다.)

댓글남기기