스트림 API가 지원하는 다양한 연산에 대해서 알아보자.
요약

필터링
.filter()
filter 메소드는 predicate(boolean을 반환하는 함수)를 인수로 받아서 true를 반환하는 모든 요소를 포함하는 스트림을 반환한다.
List<Dish> vegetarianMenu = menu.stream()
.filter(Dish::isVegetarian)
.collect(toList());
// 채식요리
.distinct()
스트림 요소의 중복을 제거하는 distinct 메소드도 지원한다.(중복 여부는 equals와 hashCode로 결정된다)
List<Integer> numbers = Arrays.asList(1,2,3,4,5,2,4);
numbers.stream()
.filter(i -> i % 2 == 0)
.distinct()
.forEach(System.out::println);
// 2
// 4
정렬시 스트림 슬라이싱
.takeWhile()
스트림 요소를 쌓다가 해당 predicate에 false인 요소가 나오면 연산을 중단하고 true인 곳까지의 스트림을 반환한다.
List<Dish> slicedMenu1 = specialMenu.stream()
.dropWhile(dish -> dish.getCalories() < 320)
.collect(toList());
// 칼로리 320 미만 요리만
.dropWhile()
takeWhile 메소드의 반대로 작용하는 메소드. predicate가 처음으로 거짓이 되는 지점까지 발견된 요소를 버린다. predicate가 거짓이 되면 그 지점에서 작업을 중단하고 남은 모든 요소를 반환한다.
List<Dish> slicedMenu2 = specialMenu.stream()
.dropWhile(dish -> dish.getCalories() < 320)
.collect(toList());
// takeWhile의 반대로 반환
스트림 축소
.limit()
지정한만큼 최대 요소 n개를 반환할 수 있다. 최대 요소에 못 미친다면 못 미친만큼 반환한다.
List<Dish> dishes = specialMenu.stream()
.filter(dish -> dish.getCalories() > 300)
.limit(3)
.collect(toList());
// 필터링 뒤 요소 3개만 반환
요소 건너뛰기
.skip()
지정한만큼 처음 n개 요소를 제외한 스트림을 반환한다. n개 이하의 요소를 포함하는 스트림에 skip(n)을 호출하면 빈 스트림이 반환된다.
List<Dish> dishes = menu.stream()
.filter(d -> d.getCalories() > 300)
.skip(2)
.collect(toList());
// 필터링한 뒤 요소 2개를 제외한 나머지 반환
매핑 mapping
.map()
함수를 인수로 받는 메소드이며 함수가 적용된 결과가 새로운 요소로 매핑된다(새로운 버전의 스트림을 만든다).
List<String> words = Arrays.asList("Modern", "Java", "In", "Action");
List<Integer> wordLengths = words.stream()
.map(String::length)
.collect(toList());
// 각 요소의 길이로 치환해서 스트림 반환
.flatMap()
각 배열을 스트림이 아니라 평면화된 스트림으로 반환한다. 이해하기 어려우니 예시로 설명하겠다.
예를들어, ["Hello", "World"] 리스트가 있다면 ["H", "e", "l", "o", "W", "r", "d"]처럼 고유 문자로 이루어진 리스트를 반환하고 싶다고 가정하자.
words.stream()
.map(word -> word.split(""))
.distinct()
.collect(toList());
// List<String[]> 반환
map 메소드를 통해서 요구사항을 만들면 String[]가 Stream에 등록되어 mapping이 진행되고 결국 map이 반환하는 값은 Stream<String[]>이 된다. flatMap을 사용하면 다음처럼 문제를 해결할 수 있다.
List<String> uniqueCharacters = words.stream()
.map(word -> word.split(""))
.flatMap(Arrays::stream)
.distinct()
.collect(toList());
// 각 단어를 개별 문자를 포함하는 배열로 변환(map 메소드)하고
// 생성된 스트림을 하나의 스트림으로 평면화한다(flatMap 메소드)
flatMap 메소드는 스트림의 각 값을 다른 스트림으로 만든 다음에 모든 스트림을 하나의 스트림으로 연결하는 기능을 수행한다.
String[] words = {"hello", "world"};
// map() 진행
Stream<String[]> -> { {"h", "e", "l", "l", "o"}, {"w", "o", "r", "l", "d"} }
// flatMap() 진행
Stream<String> -> {"h", "e", "l", "l", "o", "w", "o", "r", "l", "d"}
// distinct() 진행
{"h", "e", "l", "o", "w", "r", "d"}
검색과 매칭
.anyMatch()
스트림의 요소 중 하나라도 주어진 Predicate 조건에 만족하는 요소가 있으면 true를 반환한다.
if(menu.stream().anyMatch(Dish::isVegetarian)) {
System.out.println("채식 요리가 있습니다.");
} // 채식 요리가 있는지 확인
.allMatch()
모든 요소가 주어진 Predicate 조건을 만족하면 true를 반환한다.
boolean isHealthy = menu.stream().allMatch(dish -> dish.getCalories() < 1000);
System.out.println(isHealthy); // 모든 요리가 1000칼로리 미만인지 확인
.noneMatch()
모든 요소가 주어진 Predicate 조건에 만족하지 않으면 true를 반환한다.
boolean isHealthy = menu.stream().noneMatch(dish -> dish.getCalories() >= 1000);
System.out.println(isHealthy); // 1000칼로리 이상의 요리가 없는지 확인
.findAny()
현재 스트림에서 임의의 요소를 반환한다.
Optional<Dish> dish = menu.stream().filter(Dish::isVegetarian).findAny();
dish.ifPresent(d -> System.out.println(d.getName())); // 채식 요리 중 아무거나 하나 찾기
.findFirst()
스트림에서 첫 번째 요소를 반환한다. 순차적으로 처리할 때 사용하며 결과의 순서가 중요한 경우에 사용된다.
List<Integer> someNumbers = Arrays.asList(1, 2, 3, 4, 5);
Optional<Integer> firstSquareDivisibleByThree
= someNumbers.stream()
.map(x -> x * x)
.filter(x -> x % 3 == 0)
.findFirst();
firstSquareDivisibleByThree.ifPresent(System.out::println);
// 제곱해서 3으로 나누어떨어지는 첫 번째 요소 찾기
리듀싱 reducing
.reduce()
스트림의 모든 요소를 결합해 하나의 값으로 줄여준다. 초기값을 지정하거나 지정하지 않는 두 가지 형태가 있다. 리듀싱 연산은 스트림의 각 요소를 순회하며 누적기를 통해 값을 계산하고 반환한다.
초기값이 있는 reduce
초기값과 BinaryOperator
를 받아 스트림을 순회하며 누적 연산을 수행한다.
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
int sum = numbers.stream()
.reduce(0, Integer::sum);
System.out.println(sum);
// 모든 요소의 합 계산
초기값이 없는 reduce
초기값 없이 BinaryOperator
만을 받아 Optional을 반환한다. 요소가 없을 경우 빈 Optional
을 반환하므로 빈 경우에 대한 처리가 필요하다.
Optional<Integer> sum = numbers.stream()
.reduce(Integer::sum);
sum.ifPresent(System.out::println);
// 모든 요소의 합을 계산하되, 요소가 없을 경우 대비
최대값, 최소값 찾기
reduce
메소드를 이용하여 최대값이나 최소값을 구할 수 있다.
Optional<Integer> max = numbers.stream()
.reduce(Integer::max);
max.ifPresent(System.out::println);
// 최대값 구하기
Optional<Integer> min = numbers.stream()
.reduce(Integer::min);
min.ifPresent(System.out::println);
// 최소값 구하기
.collect()
스트림의 요소들을 변환해서 결과를 다양한 자료구조로 누적하는 데 사용된다. Collector
인터페이스를 통해 스트림 요소들을 리스트, 집합, 맵 등으로 변환할 수 있다.
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
List<Integer> numberList = numbers.stream()
.collect(Collectors.toList());
System.out.println(numberList);
// 스트림의 모든 요소를 리스트로 수집
특정 속성을 기준으로 수집
collect
를 이용해 스트림의 요소들을 특정 속성값에 따라 그룹화할 수 있다.
Map<Dish.Type, List<Dish>> dishesByType = menu.stream()
.collect(Collectors.groupingBy(Dish::getType));
System.out.println(dishesByType);
// 요리 종류별로 그룹화
리듀싱 메소드는 스트림의 각 요소를 하나의 값으로 결합하거나 다양한 자료구조로 변환하여 누적하는 기능을 수행한다. reduce
와 collect
는 리듀싱 작업에서 중요한 역할을 하며, 각각 간단한 연산부터 복잡한 누적까지 활용할 수 있다.
'Java' 카테고리의 다른 글
모던 자바 인 액션 - chapter 2, 3 동작 파라미터화, 람다 표현식 (1) | 2024.11.19 |
---|---|
모던 자바 인 액션 - chapter 1 자바 8 무슨 일이 일어나고 있는가 (0) | 2024.11.19 |
equals와 hashCode (0) | 2024.10.25 |
스트림(1) - Stream API 개요 (4) | 2024.10.23 |
람다, 메소드 참조 활용하기 (1) | 2024.10.21 |