시작하기 앞서

스트림을 이미 사용해본적이 있으나 책을 읽으며 깊게 공부해본적은 없었다. 이번 기회에 동작하는 방식과 다양한 메소드들을 사용해보고 마찬가지로 기존의 코드를 리팩토링해보자.

스트림이란?

자바 8에 추가된 API이다. 스트림을 사용함으로써 질의형 코드를 작성할 수 있다. 우테코에서 받은 피드백처럼 메세지를 보내는 형식으로 말이다. 이 형식의 장점은 임시 구현 코드들을 제거할 수 있다. 또한 데이터를 쉽게 병렬로 처리할 수 있다.

스트림 vs 컬렉션

  • 컬렉션은 계산하고자 하는 자료구조가 포함하는 모든 값을 메모리에 저장한다. 반면에 스트림은 이론적으로 요청할 때만 요소를 계산하는 고정된 자료구조이다. 스트림의 이런 특성을 게으르다고 표현한다.
  • 스트림은 한번만 탐색할 수 있다. 한번 사용한 스트림을 다시 사용할 수 없다.
  • 스트림은 내부 반복이다. 컬렉션은 외부 반복이다. 컬렉션으로 반복을 하기 위해서는 Iteration, for-each 문을 통해 반복 로직을 직접 작성해야한다. 스트림의 이 특징 덕분에 손쉽게 병렬 처리를 할 수 있다.

스트림 연산

스트림은 중간 연산과 최종 연산으로 구성된다. 중간 연산은 파이프라인으로 이어진다. 아래 코드를 보자

 List<String> names = menu.stream()<strong>.filter(dish -> dish.getCalories() > 300).map(Dish::getName).limit(3)</strong>.collect(toList());

filter부터 limit까지는 중간연산이다. collect는 최종 연산이다. 중간 연산이 끝날 때 까지 최종 연산은 수행하지 않는다. 모든 중간 연산을 마친 후 중간 연산이 합쳐진다. 최종 연산은 합쳐진 중간 연산을 받고 최종 연산을 수행한다.
만약 menu의 size가 3라면 stream의 중간 연산은 다음과 같이 실행된다.

 filter->map->limit->filter->map->limit->filter->map->limit->collect

최종 연산은 List, Integer, void 등 스트림 이외의 결과가 return된다.

스트림의 주요 기능 - 필터링

스트림 인터페이스는 filter 메소드를 지원한다. filter는 Predicate를 인수로 받아서 조건과 일치하는 모든 요소를 포함하는 스트림을 반환한다. 아래 코드를 보자.
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);

위의 코드는 filter를 통해 2, 2, 4의 스트림이 만들어지지만 distinct에 의해 하나의 2를 무시한다. 즉, 고유 요소를 리턴한다. 그 외 takeWhile, dropWhile, skip, limit와 같은 중간연산 메소드를 활용하여 다양한 필터링을 수행할 수 있다.

스트림의 주요 기능 - 매핑

스트림에서 map과 flatMap 메소드는 특정 데이터를 선택할 때 사용한다. map과 flap맵의 차이는 스트림의 평면화이다. 만약 ["Hello", "World"]list.stream().map(//TODO)의 코드를 작성한다면 map은 Stream<String<strong>[]</strong>>을 리턴한다. 반면에 list.stream().flatMap(//TODO)의 코드를 작성한다면 Stream<String>을 리턴하여 중간연산 후의 요소들이 경계가 허물어진다. 아래 코드를 보면 이해할 수 있다.

List<Integer> b = Arrays.asList(1,2,3);
List<Integer> c = Arrays.asList(4,5);
List<int[]> pairs = b.stream()
                     .flatMap(i -> c.stream()
                                      .map(j -> new int[]{<strong>i , j</strong>})
                              )
                              .collect(toList());

flatMap을 사용하여 b.stream()과 c.stream()의 i와 j를 사용할 수 있도록 만들었다.

스트림의 주요 기능 - 리듀싱

리듀스 연산을 이용해서 메뉴의 모든 칼로리의 합계를 구하시오, 메뉴에서 가장 칼로리가 높은 요리는? 같은 복잡한 질의를 표현할 수 있다. 이런 질의를 수행하려면 결과가 나올 때까지 스트림의 모든 요소를 반복적으로 처리해야 한다. 이런 질의를 리듀싱 연산이라고도 하고 폴드라고도 부른다.

콜렉션 프레임워크 - gourpingBy

콜렉션 프레임워크 - partitioningBy

  • 네이버 블러그 공유하기
  • 네이버 밴드에 공유하기
  • 페이스북 공유하기
  • 라이프코리아트위터 공유하기
  • shared
  • 카카오스토리 공유하기