/////
Search
Duplicate
4️⃣

스트림 연산

java.util.stream.Stream 인터페이스는 많은 연산을 정의하고 있다. 스트림 인터페이스의 연산을 크게 두 가지로 구분할 수 있는데, 다음은 이전에 등장했던 예제다.
List<String> names = menu.stream() .filter(dish -> dish.getCalories() > 300) .map(Dish::getName) .limit(3) .collect(toList());
Java
복사
위 예제에서 연산을 두 그룹으로 구분할 수 있다.
filter, map, limit은 서로 연결되어 파이프라인을 형성한다.
collect는 파이프라인을 실행한 다음에 종료한다.
연결할 수 있는 스트림 연산을 중간 연산, 스트림을 닫는 연산을 최종 연산이라고 한다.

1. 중간 연산

filtersorted와 같은 중간 연산은 다른 스트림을 반환한다. 따라서 여러 중간 연산을 연결해서 질의를 만들 수 있다.
중간 연산의 중요한 특징은 단말 연산을 스트림 파이프라인에 실행하기 전까지는 아무 연산도 수행하지 않는다는 것, 즉 게으르다는 것이다.
중간 연산을 합친 다음에 합쳐진 중간 연산최종 연산으로 한 번에 처리하기 때문이다.
스트림 파이프라인에서 어떤 일이 일어나는지 쉽게 확인할 수 있도록 람다가 현재 처리 중인 요리를 출력해보자.
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()); System.out.println(names);
Java
복사
다음은 프로그램 실행 결과다.
filtering:pork mapping:pork filtering:beef mapping:beef filtering:chicken mapping:chicken [pork, beef, chicken]
Java
복사
스트림의 게으른 특성 덕분에 몇 가지 최적화 효과를 얻을 수 있었다.
첫째, 300 칼로리가 넘는 요리는 여러 개지만 오직 처음 3개만 선택되었다. 이는 limit 연산 그리고 쇼트서킷이라 불리는 기법 덕분이다.
둘째, filtermap은 서로 다른 연산이지만 한 과정으로 병합되었다. 이 기법은 루프 퓨전이라고 한다.

2. 최종 연산

최종 연산은 스트림 파이프라인에서 결과를 도출한다. 보통 최종 연산에 의해 List, Integer, void 등 스트림 이외의 결과가 반환된다.
예를 들어 다음 파이프라인의 forEach는 소스의 각 요리에 람다를 적용하고 void를 반환하는 최종 연산이다. System.out.printlnforEach에 넘겨주면 menu에서 만든 스트림의 모든 요리를 출력한다.
menu.stream().forEach(System.out::println);
Java
복사

3. 스트림 이용하기

스트림 이용 과정은 다음과 같이 세 가지로 요약할 수 있다.
질의를 수행할 데이터 소스
스트림 파이프라인을 구성할 중간 연산 연결
스트림 파이프라인을 실행하고 결과를 만들 최종 연산
스트림 파이프라인의 개념은 빌더 패턴과 비슷하다.
빌더 패턴에서는 호출을 연결해서 설정을 만들고(중간 연산) 준비된 설정에 build 메서드를 호출한다.(최종 연산)