스트림에서 쓸 수 있는 다양한 기술들을 알아보겠습니다.
5.1 필터링
스트림의 요소를 선택하는 방법, 즉 프레디케이트 필터링 방법과 고유 요소만 필터링을 할 수 있다.
5.1.1 프레디케이트로 필터링
- 프레디케이트(boolean을 반환하는 함수)를 인수로 받아서 일치하는 모든 요소를 포함하는 스트림을 반환한다.
List<Dish> vegtarianMenu = menu.stream()
.filter(Dish::isVegtarian) // 채식 o x 확인하는 메서드
.collect(toList());
5.1.2 고유 요소 필터링
- 스트림은 고유 요소로 이루어진 스트림을 반환하는 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);
2 , 2, 4 가 출력해야 하지만 distinct를 사용하면 2가 중복되므로 2, 4가 출력된다.
5.2 스트림 슬라이싱
스트림의 요소를 선택하거나 스킵, 특정 크기로 줄이는 방법 등 다양한 방법의 새 기능이 생겼다.
5.2.1 프레디케이트를 이용한 슬라이싱
자바 9부터는 스트림의 요소를 효과적으로 선택할 수 있도록 takeWhile, dropWhile 메서드를 지원한다.
TAKEWHILE 활용
- TAKEWHILE 메서드는 조건식에 부합되지 않을 때 반복 작업을 중단하게 해주는 기능이다.
- filter를 사용해도 결과는 똑같이 뽑히지만 데이터가 수천 개가 넘어갈 때는 속도에서 큰 차이가 벌어질 수 있다.
List<Dish> sliceMenu1 =
specialMenu.stream()
.takeWhile(d -> d.getCalories() < 320)
.collect(toList());
DROPWHILE 활용
- 결과값이 아닌 나머지 요소를 선택할 때 쓰는 메서드가 DropWhile이다.
List<Dish> sliceMenu2
= specialMenu.stream()
.dropWhile(d -> d.getCalories() < 320)
.collect(toList());
5.2.2 스트림 축소
- 스트림은 주어진 값 이하의 크기를 갖게 해주는 limit 메서드를 지원해준다.
List<Dish> sliceMenu2
= specialMenu.stream()
.dropWhile(d -> d.getCalories() < 320)
.limit(3)
.collect(toList());
5.2.3 요소 건너뛰기
- 스트림은 n개 요소를 제외한 스트림을 반환하는 skip 메서드를 지원한다. 만약 n개 이하의 요소를 포함하는 스트림에 skip을 호출하면 빈 스트림이 반환된다.
List<Dish> sliceMenu2
= specialMenu.stream()
.dropWhile(d -> d.getCalories() < 320)
.skip(3)
.collect(toList());
5.3 매핑
Stream은 특정 객체에서 특정 데이터를 선택하는 기능을 제공해준다.
5.3.1 스트림의 각 요소에 함수 적용하기
스트림은 함수를 인수로 받는 map 메서드를 지원한다. 인수로 제공된 함수는 각 요소에 적용되며 새로운 요소로 매핑된다.
List<String> dishNames = menu.stream()
.map(Dish::getName)
.collect(toList());
Dish::getName을 map 메서드로 전달해서 스트림의 요리명을 추출할 수 있다.
List에 있는 길이들을 알고 싶을 때는?
List<String> words = Arrays.asList("asd", "java", "modern");
List<Integer> wordLengths = words.stream()
.map(String::length)
.collect(toList());
map을 이용한 매핑을 할 수가 있다.
5.3.2 스트림 평면화
List에서 고유 문자로 이루어진 결과를 반환받고 싶으면 어떤 방식을 사용해야 할까?
map, Arrays.stream, flatMap
우선 Arrays.stream() 메서드를 이용하여 배열 스트림 대신 문자열 스트림으로 만들어주고
String[] arrayOfWords = {"Hello", "World"};
Stream<String> streamOfwords = Arrays.stream(arrayOfWords);
파이프라인에 Arrays.stream() 메서드와 flatMap을 적용해주면 된다.
List<String> unique =
words.stream()
.map(word -> word.split(""))
.flatMap(Arrays::stream)
.distinct()
.collect(toList());
flatMap()은 각 요소, 스트림에 대해 여러 값을 얻은 다음 값을 단일 출력 스트림으로 병합합니다.
flatMap() 적용 전 : [[H, e, l, l, o], [w, o, r, l, d]] 적용 후 : [H, e, l, l, o, W, o, r, l, d]
5.4 검색과 매칭
데이터 집합의 특정 속성을 검색하여 데이터를 처리해주는 다양한 유틸리티를 제공해준다.
anyMatch
- 프레디케이트가 주어진 스트림에서 적어도 한 요소와 일치하는지 확인할 때 anyMatch를 이용한다.
if (menu.stream().anyMatch(Dish::isVegetarian)) {
// 채식 요리가 한개라도 있다면 출력
}
allMatch
- allMatch 메서드는 anyMatch와 달리 모든 요소를 확인한다.
NoneMatch
- noneMatch는 주어진 프레디케이트와 일치하는 요소가 없는지 확인한다.
findAny
- findAny 메서드는 현재 스트림에서 순서에 상관없이 임의의 요소를 반환한다.
- 결과가 없다면 null이 출력되므로 Optional을 함께 사용해준다.
findFirst
- 스트림에서 첫 번째 요소를 찾고 싶을 때 사용한다.
Optional
- Optional<T> 클래스는 값의 존재나 부재 여부를 표현하는 컨테이너 클래스이다.
- 값이 없을때 처리 해야할 때 사용된다.
5.5 리듀싱
반복적이고 복잡한 질의를 표현하기 위해서는 리듀스 연산을 이용한다.
함수형 프로그래밍 언어에서는 종이를 계속해서 접는것과 비슷하다고하여 폴드라고 부른다.
reduce는 두 개의 인수를 갖는다.
- 초깃값 0
- 두 요소를 조합해서 새로운 값을 만드는 BinaryOperator<T>
5.5.1 요소의 합
먼저 for-each 루프를 이용해서 숫자 합을 구하는 코드를 만들어보자.
int sum = 0;
for ( int x : numbers ) {
sum += x;
}
이 코드를 reduce를 사용해서 바꿔보자
int sum = numbers.stream().reduce(0, (a,b) -> a + b);
메서드 참조를 이용하여 더 간결하게 구현할 수 있다.
int sum = numbers.stream().reduce(0, Integer::sum);
reduce 메서드의 장점과 병렬화
단계적 반복으로 합계를 구할때는 sum 변수를 공유해야 하므로 쉽게 병렬화가 어렵다.
하지만 reduce를 이용하면 내부 반복이 추상화되면서 내부 구현에서 병렬로 reduce를 실행할 수 있게 된다.
물론 병렬로 실행하기 위해서는 연산이 어떤 순서로 실행되더라도 결과가 바뀌지 않는 구조여야 한다.
'자바 > 모던 자바 인 액션' 카테고리의 다른 글
[모던 자바 인 액션] 6장. 스트림으로 데이터 수집(2) (0) | 2022.10.24 |
---|---|
[모던 자바 인 액션] 6장. 스트림으로 데이터 수집 (1) (0) | 2022.10.07 |
[모던 자바 인 액션] 4장. 스트림이란 (0) | 2022.09.15 |
[모던 자바 인 액션] 3장. 람다 표현식 (0) | 2022.09.08 |
[모던 자바 인 액션] 2장. 동작 파라미터화 코드 전달하기 (0) | 2022.08.31 |