1. 스트림이란 무엇인가?
2. 스트림 시작하기
3. 스트림과 컬렉션
4. 스트림 연산
1. 스트림이란 무엇인가?
- 선언형(질의 표현)으로 컬렉션 데이터를 처리할 수 있음
- 루프, if문 등의 제어 블록을 사용할 필요 없다!
- 멀티스레드 코드를 구현하지 않아도 데이터를 투명하게 병렬로 처리할 수 있음
- filter, sorted, map, collect 등 여러 빌딩 블록연산을 연결해서 복잡한 데이터 처리 파이프라인을 만들 수 있다.
- 특정 스레딩 모델에 제한되지 않고, 자유롭게 어떤 상황에서든 사용할 수 있다.
- 내부적으로 단일 스레드 모델에 사용할 수 있지만 멀티코어 아키텍처를 최대한 투명하게 활용할 수 있도록 구현되어 있음
- 따라서 병렬화 과정에서 스레드와 락을 걱정할 필요가 없다!
public static List<Dish> sort_byJava7(List<Dish> menu){
List<Dish> lowCaloricDishes = new ArrayList<>();
for(Dish dish: menu){
if (dish.getCalories() < 400){
lowCaloricDishes.add(dish);
}
}
Collections.sort(lowCaloricDishes, new Comparator<Dish>() {
@Override
public int compare(Dish o1, Dish o2) {
return -Integer.compare(o1.getCalories(), o2.getCalories());
}
});
return lowCaloricDishes;
}
public static List<Dish> sort_byJava8(List<Dish> menu){
//return menu.stream().filter(d-> d.getCalories() < 400).sorted(comparing(Dish::getCalories)).collect(Collectors.toList());
// parallel
return menu.parallelStream().filter(d-> d.getCalories() < 400).sorted(comparing(Dish::getCalories).reversed()).collect(Collectors.toList());
}
2. 스트림 시작하기
스트림
데이터 처리 연산을 지원하도록 소스에서 추출된 연속된 요소
- 연속된 요소
- 컬렉션과 마찬가지로 스트림은 특정 요소 형식으로 이루어진 연속된 값 집합의 인터페이스를 제공함. 컬렉션은 List, Set, Map, Queue로 이루어진 자료구조이고, 요소 저장 및 접근 연산이 주를 이룸. 반면 스트림은 filter, map, sorted와 같이 표현 계산식이 주를 이룸. 컬렉션은 데이터를 주로 다루고, 스트림은 계산을 주로 다룸
- 소스
- 스트림은 컬렉션 , 배열, IO자원 등의 데이터 제공 소스로부터 데이터를 소비함. 정렬된 컬렉션으로 스트림을 생성하면 정렬이그대로 유지됨. 즉, 리스트로 스트림을 만들면 스트림 요소는 리스트의 요소와 같은 순서를 가짐
- 데이터 처리 연산
- 스트림은 함수형 프로그래밍 언어에서 일반적으로 지원하는 연산과 데이터베이스와 비슷한 연산을 지원함. 예를 들어 filter, map, reduce, find, match, sort 등으로 데이터를 조작할 수 있음. 스트림 연산은 순차적으로 또는 병렬로 실행할 수 있음
기능
- filter: 람다를 인수로 받아 스트림에서 특정 요소를 제외시킴
- map: 람다를 이용해서 한 요소를 다른 요소로 변환하거나 정보를 추출함
- menu.stream().filter(d->d.getCalories() > 300).map(Dish::getName)
- Dish 객체에서 getName 메서드를 통해 String 정보만 빼내라
- limit: 정해진 개수 이상의 요소가 스트림에 저장되지 못하게 스트림 크기를 축소함
- collect: 스트림을 다른 형식으로 변환
public class HighCaloricFilter implements Operation{
@Override
public List<Dish> operate(List<Dish> menu) {
return menu.stream() // menu에서 스트림을 얻음
.filter(d->d.getCalories() > 300) // 첫 번째 고칼로리 요리 필터
.limit(3) // 선착순 3개만 선택
.collect(Collectors.toList()); // 결과를 다른 리스트로 저장
}
}
특징
- 파이프라이닝: 스트림은 스트림 연산끼리 연결해서 파이프라이닝을 구성하도록 스트림 자신을 반환함.
- 한번만 탐색 가능: 한 번 탐색한 요소를 다시 탐색하려면 초기 데이터 소스에서 새로운 스트림을 만들어야 함
List<String> title = Array.asList("Java8". "In", "Action");
Stream<String> s = title.stream();
s.forEach(System.out::println); // title의 각 단어를 출력함
//s.forEach(System.out::println); // 위에서 이미 스트림을 소비해서 실행안됨
- 내부반복: 반복자를 이용해서 명시적으로 반복하는 컬렉션과 달리 스트림은 내부 반복을 지원함
- 내부 반복이 좋은 이유
- 코드 길이가 짧아짐
- 내부 동작이 캡슐화되어 최적화가 쉬워짐.
- 데이터 표현과 하드웨어를 화용해 알아서 병렬 구현을 해주지만, 외부 반복의 경우 코딩하는 사람이 직접 짜야함
- 내부 반복이 좋은 이유
// 컬렉션의 외부 반복: for-each
List<String> names = new ArrayList<>();
for(Dish dish: menu){
names.add(dish.getName());
}
// 컬렉션의 외부 반복: 반복자
names = new ArrayList<>();
Iterator<String> iterator = menu.iterator();
while(names.hasNext()){
Dish dish = iterator.next();
names.add(dish.getName());
}
// 스트림: 내부 반복
List<String> names = menu.stream().map(Dish::getName) //map 메서드를 getName메서드로 파라미터화해서 요리명 추출
.collect(toList()); // 파이프라인을 실행함, 반복자는 필요 없음
3. 스트림과 컬렉션
컬렉션과 스트림 모두 연속된 요소 형식의 값을 저장하는 자료구조의 인터페이스를 제공함
차이점: 데이터를 언제 계산하는가?
- 컬렉션
- 현재 자료구조가 포함하는 모든 값을 메모리에 저장함. 즉, 모든 요소가 컬렉션에 추가되기 전에 계산되어야 함.
- 컬렉션에 요소를 추가하거나 컬렉션의 요소를 삭제할 수 있음
- 스트림
- 요청할 때만 요소를 계산하는 고정된 자료구조. 사용자가 요청하는 값만 추출하고, 사용자가 데이터를 요청할때만 값을 계산함.
- 스트림에 요소를 추가하거나 스트림에서 요소를 제거할 수 없음
예시
- 스트림: 영화 스트리밍 서비스. 영화의 모든 타임라인 데이터를 다 받을 때까지 기다릴 필요 없음
- 컬렉션: DVD. 이미 전부 저장되어 있는 영화를 꺼내서 씀
4. 스트림 연산
List<String> names = menu.stream().filter(dish->dish.getCalories()>300) // 중간연산
.map(Dish::getName).limit(3) // 중간연산
.collect(toList()); // 최종연산
- filter, map, limit은 서로연결되어 파이프라인을 형성함
- collect로 파이프라인을 실행한 다음 닫음
중간연산
- filter, sorted, map. limit, distinct와 같은 중간 연산은 다른 스트림을 반환함
- 여러 중간 연산을 연결해서 질의를 만들 수 있음
- lazy: 최종 연산을 스트림 파이프라인에서 실행하기 전까지 아무 연산도 수행하지 않음. 중간 연산을 합친 다음에 합쳐진 중간 연산을 최종연산으로 한번에 처리
public List<String> operate_getString(List<Dish> menu){
return 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(Collectors.toList());
}
최적화
- filter 연산: 300칼로리가 넘는 요리는 3개보다 많은데 처음 3개만 선택됨
- limit 연산, 쇼트서킷 기법 때문
- filter와 map은 서로 다른 연산이지만 한 과정으로 병합됨
- 루트 퓨전 기법
최종 연산
- 스트림 파이프라인에서 결과를 도출함
- 최종 연산에 의해 List, Integer, void 등 스트림 이외의 결과가 반환됨
- forEach, count, collect
'백엔드 > 모던자바인액션' 카테고리의 다른 글
ch16. CompletableFuture: 안정적 비동기 프로그래밍 (0) | 2023.04.04 |
---|---|
ch15. CompletableFuture와 리액티브 프로그래밍 컨셉의 기초 (0) | 2023.03.23 |
[ch3] 람다 표현식 (2) (0) | 2022.09.03 |
[ch3] 람다 표현식 (1) (0) | 2022.09.02 |
[ch2] 동작 파라미터화 코드 전달하기 (0) | 2022.09.01 |
댓글