스트림, 스트림 활용
2023. 2. 12. 14:25
스트림
- 스트림이란 무엇인가?
- 컬렉션과 스트림
- 내부 반복과 외부 반복
- 중간 연산과 최종 연산
스트림
- 선언형으로 컬렉션 데이터를 처리할 수 있는 기술
- 스트림을 이용하면 멀티스레드 코드를 구현하지 않아도 데이터를 투명하게 병렬로 처리가능
- 투명하게의 의미는 무엇일까? -> 7장에 계속
// java 8 이전
void Test() {
// 칼로리가 400 미만인 메뉴 filter
List<Dish> lowCaloricDishes = new ArrayList<>();
for(Dish dish : menu) {
if (dish.getCalories() < 400) {
lowCaloricDishes.add(dish);
}
}
// 오름차순으로 컬렉션 정렬
Collections.sort(lowCaloricDishes, new Comparator<Dish>() {
public int compare(Dish dish1, Dish, dish2) {
return Integer.compare(dish1.getCalories(), dish2.getCalories());
}
});
// 메뉴들의 이름만 따로 정리
List<String> lowCaloricDishesName = new ArrayList<>();
for(Dish dish : lowCaloricDishes) {
lowCaloricDishesName.add(dish.getName());
}
}
// java 8
import static java.util.Comparator.comparing;
import static java.util.stream.Collectors.toList;
void Test() {
List<String> lowCaloricDishesName =
menu.paralleStream()
.filter(d -> d.getCalories() < 400) // 칼로리 400 미만 메뉴 선정
.sorted(comparing(Dishes::getCalories)) // 정렬
.map(Dish::getName) // 이름 가져오기
.collect(toList());
}
스트림의 특징
- 선언형으로 코드 구현 가능
- for문이나 if문 블록으로 제어하는 것이아닌, '찾아줘', '걸러줘' 식의 수행이 가능
- filter, sorted, map 처럼 여러 빌딩 블록 연산을 연결하여 복잡한 데이터 처리 파이프라인 생성 가능
- 병렬처리 가능 (성능이 좋아짐)
스트림 시작하기
스트림의 정의
- 데이터 처리 연산을 지원하도록 소스에서 추출된 연속된 요소
- 컬렉션과 마찬가지로 스트림은 특정 요소 형식으로 이루어진 연속된 값 집합의 인터페이스 제공
- 파이프라이닝 : 대부분의 스트림 연산은 스트림 연산끼리 연결해서 거대한 커다란 파이프 라인을 구성
- 내부반복 : 반복자를 이용해서 명시적으로 반복하는 컬렉션과 달리 내부 반복 지원
- 연산
- filter : 특정 요소 제외
- map : 한 요소를 다른 요소로 변환하거나 정보 추출
- limit : 스트림 크기 축소
- collect : 스트림을 다른 형식으로 변환
스트림과 컬렉션
컬렉션과의 차이
- 데이터를 언제 계산하느냐가 가장 큰 차이
- 컬렉션은 현재 자료구조가 포함하는 모든 값을 메모리에 저장
- 스트림은 이론적으로 요청할 떄만 요소를 계산하는 고정된 자료구조
딱 한번만 탐색할수 있다.
반복자와 마찬가지로 스트림도 한 번만 탐색 가능
탐색된 스트림의 요소는 소비됨(휘발성)
재탐색하려면 새로운 스트림 생성해야함
List<String> title = Array.asList("Java8", "In", "Action");
Stream<String> s = title.stream();
s.forEach(System.out::println); // title의 각 단어 출력
s.forEach(System.out::println); // 스트림이 이미 소비되어서 출력 안됨
외부 반복과 내부 반복
- 컬렉션 인터페이스를 사용하려면 사용자가 직접 요소를 반복해야함 (ex) for-each)
- 외부 반복이라고 함
List<String> names = new ArrayList<>();
for(Dish dish: menu) { // 메뉴 리스트를 명시적으로 순차 반복
names.add(dish.getName());
}
- 스트림 라이브러리는 내부 반복
List<String> names = menu.stream().map(Dish::getName).collect(toList());
내부 반복의 장점
- 작업을 투명하게 병렬로 처리 가능
- 최적화된 다양한 순서로 처리 가능
스트림 연산
중간 연산
- filter나 sorted같은 중간 연산은 다른 스트림을 반환
최종 연산
- 최종 연산은 스트림 파이프라인에서 결과를 도출함
- 스트림 이외의 결과과 반환되면 최종 연산이다.
스트림 활용
- 필터링 슬라이싱, 매칭
- 검색, 매칭, 리듀싱
- 특정 범위의 숙자와 같은 숫자 스트림 사용하기
- 다중 소스로부터 스트림 만들기
- 무한 스트림
필터링
프레디케이트 활용
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);
스트림 슬라이싱
프레디케이트 활용
- 기본형
// 320 칼로리 이하의 음식을 필터링 하는 경우
List<Dish> filteredMenu
= specialMeun.stream()
.filter(dish -> dish.getCalories() < 320)
.collect(toList());
- takewhile 활용
- 정렬이 되어있다 가정했을때 칼로리 320 넘어가는 경우에 작업 중단해줌
List<Dish> slicedMenu1
= specialMenu.steram()
.takeWhile(dish -> dish.getCalories() < 320)
.collect(toList());
- dropwhile 활용
- 나머지 요소 선택하는 방법
- 320 칼로리보다 큰 요소를 검색하는 경우
- takewhile과 정반대 작업
List<Dish> slicedMenu1
= specialMenu.steram()
.dropWhile(dish -> dish.getCalories() < 320)
.collect(toList());
- 스트림 축소
- limit 사용
List<Dish> dishes
= specialMeun.stream()
.filter(dish -> dish.getCalories() > 300)
.limit(3)
.collect(toList());
- 요소 건너뛰기
- 처음 n개 요소를 제외한 스트림을 반환하는 skip 메소드 지원
List<Dish> dishes
= specialMeun.stream()
.filter(dish -> dish.getCalories() > 300)
.skip(2)
.collect(toList());
매핑
스트림의 각 요소에 함수 적용
- 스트림은 함수를 인수로 받는 map 메소드 지원
- 함수를 적용한 결과가 새로운 요소로 매핑
List<String> dishNames = menu.stream()
.map(Dish::getName)
.collect(toList());
스트림 평면화
- 리스트에서 고유 문자로 이루어진 리스트를 반환해보자
- ex) ["Hello", "World"] -> ["H", "e", "l", "o", "W", "r", "d"]
words.stream()
.map(word -> word.split(""))
.distinct()
.collect(toList());
- 그러나 이렇게 할 경우 Stream<String[]>이 반환됨
- map으로 전달한 람다는 각 단어의 String[]을 반환하기 때문
- 우리는 Stream<String>을 반환하고 싶음
- flatmap을 사용해보자
List<String> uniqueCharacters =
words.stream()
.map(word -> word.split(""))
.flatMap(Arrays::stream) // 생성된 스트림을 하나의 스트림으로 평면화
.distint()
.collect(toList());
검색과 매칭
- 적어도 한 요소와 일치하는지 확인 (boolean)
- anyMatch
if (menu.stream().anyMatch(Dish::isVegeterian)) {
// 추후 작업 ...
}
- 모든 요소와 일치하는지 확인
- allmatch
boolean isHealthy = menu.stream()
.allMatch(dish -> dish.getCalories() < 1000);
- 일치하는 요소가 없는지 확인
- nonematch
boolean isHealthy = menu.stream()
.noneMatch(d -> d.getCalories() >= 1000);
- 요소 검색
- findAny
- 찾는 즉시 종료됨
- findFirst 사용하면 첫번째 요소 반환(논리적인 아이템 순서에서) -> 병렬실행시 필요하다
Optional<Dish> dish =
menu.stream()
.filter(Dish::isVegeterian)
.findAny();
리듀싱
- 리듀싱 연산이란?
- 모든 스트림 요소를 처리해서 값으로 도출하는 연산
- 폴드라고도 한다
- 요소의 합, 최댓값, 최솟값
// for-each 사용
int sum = 0;
for (int x : numbers) {
sum += x;
}
// reduce 사용
int sum = numbers.stream().reduce(0, (a,b) -> a+b); //0은 초기값
// 초기값이 없는 경우
Optional<Integer> sum = numbers.stream().reduce((a,b) -> (a+b));
// 최댓값
Optional<Integer> max = numbers.stream().reduce(Integer::max);
// 최솟값
Optional<Integer> min = numbers.stream().reduce(Integer::min);
- reduce 메소드의 장점
- 병렬화 가능
'책 > 모던 자바 인 액션' 카테고리의 다른 글
동적 파라미터화 코드 전달하기 (0) | 2022.12.05 |
---|