필터링
filter 메서드
Predicate 를 이용해 특정 조건에 맞는 요소만 남기는 필터링이 가능하다.
단, 별도의 연산을 추가하지 않는다면, filter 메서드는 모든 요소에 대해 검사하기 때문에 전체 순회가 발생한다.
distinct 메서드
equals 메서드와 hashCode 메서드로 결정되는 동일한 요소는 하나만 포함하도록 필터링한다.
equals 와 hashCode가 적절하게 정의되어있지 않으면 의도대로 동작하지 않을 수 있기 때문에 주의해야 한다.
스트림 슬라이싱
stream의 일부 영역을 잘라낸 stream을 얻는 메서드 들이다.
takeWhile & dropWhile
Predicate를 이용해 각각 조건에 맞지 않는 요소가 처음 나왔을 때까지 탐색한 요소를 포함하는 stream, 조건에 맞지 않는 요소가 처음 나왔을 때까지 탐색한 요소를 제외한 steream 을 생성한다.
List<Dish> sliceMenu = totalMenus.stream()
.takeWhile(dish -> dish.getPrice() < 3000)
.collect(toList());
Java
복사
만약, totalMenus가 이미 가격 순으로 오름차순 정렬되었다면 위 코드를 통해 가격이 3000 미만인 메뉴가 선택될 것이다.
List<Dish> sliceMenu = totalMenus.stream()
.dropWhile(dish -> dish.getPrice() < 3000)
.collect(toList());
Java
복사
반대로 dropWhile을 사용한다면 가격이 3000 이상인 메뉴가 선택될 것이다.
두 메서드 모두 무한스트림에서도 잘 동작한다.
limit
앞에서 n 개의 요소만 선택하여 stream으로 만들어주는 메서드다.
List<Dish> sliceMenu = totalMenus.stream()
.filter(dish -> dish.getPrice() > 3000)
.limit(3)
.collect(toList());
Java
복사
위와 같이 사용해 가격이 3000을 초과하는 메뉴 중 3개를 선택해 List로 만들 수 있다. 스트림이 정렬되어있다면 정렬된 상태로, 그렇지 않다면 그렇지 않은 상태에서 앞에서부터 n 개의 요소를 선택한다.
반대로 앞에서 n개의 요소만 제외한 stream을 만들어주는 skip() 메서드도 있다.
변환
map
stream의 각 요소를 순서를 유지한 채 다른 객체로 변환하여 새로운 stream으로 만들어주는 메서드.
매개변수로 Function을 받기 때문에, 스트림의 요소의 메서드를 호출하는 등 여러 방법으로 변환할 수 있다. 논리적으로 요소를 어떻게 다른 형태로 변환할 것인지 표현한 것을 매개변수로 넘기는 것이다.
List<Integer> menuPrices = totalMenus.stream()
.map(dish -> dish.getPrice()
.collect(toList());
Java
복사
위와 같이 사용해 메뉴 가격을 가지는 리스트를 생성할 수 있다.
flatMap
스트림의 각 요소를 스트림으로 바꾼 뒤, 바뀐 스트림을 하나의 스트림으로 바꿔주는 메서드다.
스트림 평탄화를 제공하는 메서드다. 이는 스트림의 요소가 더 작은 수준의 요소로 이루어진 스트림으로 표현될 수 있는 경우, 스트림을 더 작은 수준의 요소로 이루어진 스트림으로 변환하는 것이다.
예를 들어, “Hello World” 라는 문자열을 split 을 이용해 배열로 만든 뒤 Arrays.stream() 을 이용해 스트림으로 만들면 “Hello” 와 “World” 라는 두 문자열을 요소로 하는 stream 이 만들어진다. 이 각각의 문자열 역시 단일 문자로 이루어진 더 작은 문자열의 Stream으로 표현될 수 있다. 각각 “H”, “e”, “l”, “l”, “o” 그리고 “W”, “o”, “r”, “l”, “d” 를 포함하는 스트림이 된다. flatMap을 사용하면 이를 하나의 스트림으로 묶을 수 있다.
var collect = words.stream()
.map(word -> word.split(""))
.flatMap(Arrays::stream)
.collect(toList());
Java
복사
위와 같은 코드에서 collect 는 List<String> 이 되지만,
var collect = words.stream()
.map(word -> word.split(""))
.map(Arrays::stream)
.collect(toList());
Java
복사
flatMap을 map으로 바꾸면 List<Stream<String>>이 된다.
검색과 매칭
anyMatch
스트림의 요소 중 적어도 하나가 Predicate를 만족하는지 확인하는 메서드다.
모든 요소에 대한 Predicate.test(요소) 의 결과를 or 연산 한 것이다.
allMatch
스트림의 모든 요소가 Predicate를 만족하는지 확인하는 메서드다.
모든 요소에 대한 Predicate.test(요소) 의 결과를 and 연산 한 것이다.
noneMatch
스트림의 모든 요소가 Predicate를 만족하지 않는지 확인하는 메서드다.
모든 요소에 대한 Predicate.test(요소) 의 부정을 and 연산 한 것이다.
findAny
스트림의 요소를 탐색하다 처음 발견한 Predicate 를 만족하는 요소를 Optional로 감싸 반환하는 메서드.
일반적인 직렬 스트림에서는 탐색 순서가 스트림 내부의 요소 순서와 같으므로 findFirst와 결과가 같지만, 병렬 스트림에서는 모든 쓰레드에서 가장 먼저 탐색된 것을 반환하므로 조건을 만족하는 요소가 여러 개 있을 때, 무엇이 반환될지 알 수 없다.
findFirst
스트림의 순서상 가장 앞에 있는 Predicate 를 만족하는 요소를 Optional로 감싸 반환하는 메서드.
병렬 스트림에서 사용하더라도 스트림의 순서상 가장 앞에 있는 요소를 반환한다.
리듀싱
스트림의 모든 요소에 어떤 연산을 누적해 수행해 하나의 결과로 만드는 연산이다.
T reduce(T identity, BinaryOperator<T> accumulator)
초기값을 identity 로 하고, 이것과 스트림의 요소 하나를 accumulator 를 이용해 연산한뒤 그 결과로 identity 를 대체한다. 이를 스트림의 모든 요소에 반복하는 것이 이 메서드다.
예를 들어 아래 코드를 실행하면
List<Integer> numbers = List.of(1,4,5,7,8,9,3,2);
int reduce = numbers.stream().reduce(0, (a, b) -> a + b));
Java
복사
reduce 에는 numbers의 모든 요소의 합이 된다. 참고로 Integer.sum(int a, int b)가 이미 정의되어 있으므로 위 코드는 메서드 참조를 이용해
List<Integer> numbers = List.of(1,4,5,7,8,9,3,2);
int reduce = numbers.stream().reduce(0, Integer::sum);
Java
복사
으로 바꿀 수 있다.
Optional<T> reduce(BinaryOperator<T> accumulator)
이 메서드는 별도의 초기값을 넘겨주지 않고, 스트림의 요소 하나를 선택해 이를 초기값으로 사용한다. 따라서, 스트림이 비어있으면 연산의 결과를 정의할 수 없기 때문에 Optional을 반환하는 것이다.
리스트의 모든 요소의 합을 구하는 앞의 코드를 이 메서드를 이용해 이렇게 바꿀 수 있다.
List<Integer> numbers = List.of(1,4,5,7,8,9,3,2);
int reduce = numbers.stream()
.reduce(Integer::sum)
.orElse(0);
Java
복사
최대값과 최소값 찾기
모든 요소의 합이나 곱 등의 단순한 산술 연산은 직관적으로 이해할 수 있다. 이 뿐 아니라 reduce 를 활용하면 최대값이나 최소값을 찾을 수도 있다.
List<Integer> numbers = List.of(1,4,5,7,8,9,3,2);
Optional<Integer> reduce = numbers.stream().reduce(Integer::max);
Java
복사
기본형 특화 스트림
자바에서는 int, double, long 등의 기본형을 객체처럼 사용하기 위해 Wrapper 클래스를 제공한다. 기본형을 Wrapper 클래스의 인스턴스로 변환하는 것을 Boxing(박싱)이라 하고, 반대를 Unboxing(언박싱)이라 한다.
박싱 비용을 아끼기 위해 IntStream, LongStream, DoubleStream을 제공한다.
일반 스트림 ⇒ 기본형 특화 스트림
mapToInt를 사용해 IntStream으로 변환할 수 있다. mapToLong, mapToDouble 을 이용해 각각 LongStream, DoubleStream 으로 변환할 수 있다.
기본형 특화 스트림 ⇒ 일반 스트림
boxed 를 사용해 일반 스트림으로 변환할 수 있다. IntStream 은 Stream<Integer> 로 변환된다. 다른 기본형 특화 스트림 역시 Stream<랩퍼 클래스>로 변환된다.