스트림
을 이용하면 선언형으로 컬렉션 데이터를 처리할 수 있다. 또한 멀티스레드 코드를 구현하지 않아도 데이터를 병렬로 처리할 수 있다.
List<String> lowCaloricDishesName -
menu.stream()
.filter(d -> d.getCalories() < 400)
.sorted(comparing(Dish::getCalories))
.map(Dish::getName)
.collect(toList()
stream()을 parallelStream()으로 바꾸면 위의 코드를 멀티코어 아키텍처에서 병렬로 실행할 수 있다.
List<String> lowCaloricDishesName -
menu.parallelStream()
.filter(d -> d.getCalories() < 400)
.sorted(comparing(Dish::getCalories))
.map(Dish::getName)
.collect(toList()
filter(or sorted, map, collect) 같은 연산은 고수준 빌딩 블록
으로 이루어져 있으므로 특정 스레딩 모델에 제한되지 않고 자유롭게 어떤 상황에서든 사용할 수 있다.
Map<Dish.Type, List<Dish>> dishesByType =
menu.stream().collect(groupingBy(Dish::getType));
위의 코드는 Map내부의 형식에 따라 요리를 그룹화한다.
스트림 API의 특징을 아래와 같이 요약할 수 있다.
스트림이란 데이터 처리 연산을 지원하도록 소스에서 추출된 연속된 요소
로 정의할 수 있다.
자료구조
이므로 컬렉션에서는 시간과 공간의 복잡성과 관련된 요소 저장 및 접근 연산이 주를 이룬다.스트림의 주요 특징
게으름, 쇼트서킷
같은 최적화도 얻을 수 있다.List<String> threeHighCaloricDishNames =
menu.stream()
.filter(dish -> dish.getCalories() > 300)
.map(Dish::getName)
.limit(3)
.collect(toList());
데이터 소스
는 요리 리스트다. 데이터 소스는 연속된 요소를 스트림에 제공한다. 다음으로 스트림에 filter, map, limit, collect로 이어지는 일련의 데이터 처리 연산을 적용한다. collect를 제외한 모든 연산은 서로 파이프라인을 형성할 수 있도록 스트림을 반환한다.
데이터를 언제
저장하느냐가 컬렉션과 스트림의 큰 차이다.
사용자가 데이터를 요청할 때만 값을 계산한다.
반복자와 마찬가지로 스트림도 딱 한 번만 탐색할 수 있다. 탐색된 스트림의 요소는 소비된다.
스트림은 단 한 번만 소비할 수 있다
컬렉션 인터페이스를 사용하려면 사용자가 직접 요소를 반복해야한다. (ex. for-each) ⇒ 외부반복
스트림 라이브러리는 내부 반복
을 사용한다.
// 외부반복
List<String> name = new ArrayList<>();
for (Dish dish: menu) {
names.add(dish.getName());
}
// 내부반복
List<String> names = menu.stream()
.map(Dish::getName)
.collect(toList());
컬렉션은 외부적으로 반복, 즉 명시적으로 컬렉션 항목을 하나씩 가져와서 처리한다.
내부반복을 이용하면 작업을 투명하게 처리하거나 더 최적화된 다양한 순서로 처리할 수 있다.
스트림 라이브러리의 내부 반복은 데이터 표현과 하드웨어를 활용한 병렬성 구현을 자동으로 선택한다. for-each를 이용하는 외부 반복에서는 병렬성을 스스로 관리
해야 한다.
스트림 인터페이스의 연산을 크게 두 가지로 구분할 수 있다.
List<String> names = menu.stream() // 요리 리스트에서 스트림 얻기
.filter(dish -> dish.getCalories() > 300) // 중간 연산
.map(Dish::getName) // 중간 연산
.limit(3) // 중간 연산
.collect(toList()); // 스트림을 리스트로 변환
연결할 수 있는 스트림 연산을 중간 연산이라고 하며, 스트림을 닫는 연산을 최종 연산이라고 한다.
filter나 sorted와 같은 중간 연산은 다른 스트림을 반환한다. ⇒ 여러 중간 연산을 연결해서 질의를 만들 수 있다.
중간 연산은 단말 연산을 스트림 파이프라인에 실행하기 전까지는 아무 연산도 수행하지 않는다는 것, 즉 게으르다
는 것이다. 중간 연산을 합친 다음에 합쳐진 중간 연산을 최종 연산으로 한 번에 처리하기 때문이다.
쇼트서킷이라는 기법
최종 연산은 스트림 파이프라인에서 결과를 도출한다. 보통 최종 연산에 의해 List, Integer, void등 스트림 이외의 결과가 반환된다.
스트림 이용 과정은 세 가지로 요약할 수 있다.
스트림 파이프라인의 개념은 빌더 패턴
이랑 비슷하다.
https://inpa.tistory.com/entry/GOF-💠-빌더Builder-패턴-끝판왕-정리 ⇒ 빌더 패턴에 대한 자세한 정리