스트림의 요소를 선택하는 방법, 즉 프레디케이트 필터링 방법과 고유 요소만 필터링하는 방법을 배운다.
스트림 인터페이스는 filter 메서드를 지원하는데, 해당 메서드에서는 프레디케이트(불리언을 반환하는 함수)
를 인수로 받아서 프레디케이트와 일치하는 모든 요소를 포함하는 스트림을 반환한다.
List<Dish> vegetarianMenu = menu.stream()
.filter(Dish::isVegetarian)
.collect(toList());
스트림은 고유 요소로 이루어진 스트림을 반환하는 distinct 메서드도 지원한다. (고유 여부 → 객체의 hashCode, equals로 결정됨.)
List<Integer> numbers = Arrays.asList(1, 2, 1, 3, 3, 2, 4);
numbers.stream()
.filter(i -> i % 2 == 0)
.distinct()
.forEach(System.out::println);
스트림의 요소를 선택하거나 스킵하는 방법에 대해 다룬다.
takeWhile, dropWhile 두 가지 메서드를 지원한다.
List<Dish> slicedMenu1
= specialMenu.stream()
.takeWhile(dish -> dish.getCalories() < 320)
.collect(toList());
List<Dish> slicedMenu2
= specialMenu.stream()
.dropWhile(dish -> dish.getCalories() < 320)
.collect(toList());
스트림은 주어진 값 이하의 크기를 갖는 새로운 스트림
을 반환하는 limit(n)메서드를 지원한다.
스트림이 정렬되어 있으면 최대 요소 n개를 반환할 수 있다.
정렬되지 않아도 사용이 가능하다.
스트림은 처음 n개 요소를 제외한 스트림을 반환하는 skip(n) 메서드를 지원한다. n개 이하의 요소를 포함하는 스트림에 skip(n)을 호출하면 빈 스트림이 반환된다.
limit(n)과 skip(n)은 상호 보완적인 연산을 수행한다.
스트림 API의 map과 flatMap 메서드는 특정 데이터를 선택하는 기능을 제공한다.
스트림은 함수를 인수로 받는 map 메서드를 지원한다.
List<String> words = Arrays.asList("Modern", "Java", "In", "Action");
List<Integer> wordLengths = words.stream()
.map(String::length)
.collect(toList());
각 요리명의 길이를 알고싶다면? → 다른 map 메서드를 연결할 수 있다.
List<Integer> dishNameLengths = menu.stream()
.map(Dish::getName)
.map(String::length)
.collect(toList());
map을 이용해서 각 단어의 길이 반환을 하였고, 이를 응용해서 리스트에서 고유 문자로 이루어진 리스트를 반환해본다.
words.stream()
.map(word -> word.split(""))
.distinct()
.collect(toList());
위의 map 메소드가 반환한 스트림의 형식은 Stream<String[]>이다. 우리가 원하는 것은 문자열의 스트림을 표현할 Stream이다. ⇒ flatMap
을 활용하여 해결할 수 있다.
String[] arrayOfWords = {"Goodbye", "World"};
Stream<String> streamOfWords = Arrays.stream(arrayOfWords);
words.stream()
.map(word -> word.split("")) // 각 단어를 개별 문자열 배열로 반환
.map(Arrays::stream) // 각 배열을 별도의 스트림으로 생성
.distinct()
.collect(toList());
List<String> uniqueCharacters =
words.stream()
.map(word -> word.split("")) // 각 단어를 개별 문자를 포함하는 배열로 변환
.flatMap(Arrays::stream) // 생성된 스트림을 하나의 스트림으로 평면화
.distinct()
.collect(toList());
anyMatch 메서드를 이용하면된다.
if (menu.stream().anyMatch(Dish::isVegetarian)) {
System.out.println("The menu is (somewhat) vegetarian friendly!!");
}
anyMatch는 불리언을 반환하므로 최종 연산이다.
allMatch는 스트림의 모든 요소가 주어진 프레디케이트와 일치하는지 검사한다.
boolean isHealthy = menu.stream()
.allMatch(dish -> dish.getCalories() < 1000);
anyMatch, allMatch, noneMatch 세 메서드는 스트림 쇼트서킷
기법, 즉 자바의 &&, ||와 같은 연산을 활용한다.
Optional이란?
List<Integer> someNumbers = Arrays.asList(1, 2, 3, 4, 5);
Optional<Integer> firstSquareDivisibleByThree =
someNumbers.stream()
.map(n -> n * n)
.filter(n -> n % 3 == 0)
.findFirst();
리듀스 연산을 이용해서 스트림 요소를 조합하여 더 복잡한 질의를 표현하는 방법을 설명한다.
이를 수행하려면 Integer 같은 결과가 나올 때까지 스트림의 모든 요소를 반복적으로 처리해야하는데, 이런 질의를 리듀싱 연산
이라고 한다.
리스트의 숫자 요소를 더하는 코드.
리스트에서 하나의 숫자가 남을 때까지 reduce 과정을 반복하는데, 두 개의 파라미터를 사용한다.
reduce를 이용해서 스트림의 모든 요소를 더할 수 있다.
int sum = numbers.stream().reduce(0, (a, b) -> a + b);
reduce는 두 개의 인수를 갖는다.
두 요소를 조합해서 새로운 값을 만드는 BinaryOperator
int sum = numbers.stream().reduce(0, Integer::sum);
초깃값 없음
초깃값을 받지 않도록 오버로드된 reduce도 있으나 Optional 객체를 반환한다.
reduce를 활용해서 스트림에서 최댓값과 최솟값을 찾을 수도 있다. 이때 reduce는 두 인수를 받는다.
Optional<Integer> max = numbers.stream().reduce(Integer::max) // 스트림의 최댓값
Optional<Integer> min = numbers.stream().reduce(Integer::min) // 스트림의 최솟값
int calories = menu.stream()
.map(Dish::getCalories)
.reduce(0, Integer::sum);
위의 코드에는 박싱 비용이 숨어있다. 내부적으로 합계를 계산하기 전에 Integer를 기본형으로 언박싱해야한다.
map 메서드가 Stream를 생성하기 때문에 sum 메서드를 직접 호출할 수는 없다. 그렇지만 기본형 특화 스트림
을 제공함으로써 문제점을 해결할 수 있다.
스트림을 특화 스트림으로 변환할 때는 mapToInt, mapToDouble, mapToLong 세 가지 메서드를 가장 많이 사용한다. Stream 대신 특화된 스트림을 반환한다.
int calories = menu.stream()
.mapToInt(Dish::getCalories)
.sum();
객체 스트림으로 복원하기
아래 코드와 같이 boxed 메서드를 이용해서 특화 스트림을 일반 스트림으로 변환할 수 있다.
IntStream intStream = menu.stream().mapToInt(Dish::getCalories); // 스트림을 숫자 스트림으로 변환
Stream<Integer> stream = intStream.boxed(); // 숫자 스트림을 스트림으로 변환
기본값 : OptionalInt
IntStream에서 최댓값을 찾을 때는 0이라는 기본값 때문에 잘못된 결과가 도출될 수 있다. → 값이 존재하는지 여부를 가리킬 수 있는 컨테이너 클래스 Optional 사용
임의의 수를 인수로 받는 정적 메서드 Stream.of를 이용해서 스트림을 만들 수 있다.
Stream<String> stream = Stream.of("Modern ", "Java ", "In ", "Action"); stream.map(String::toUpperCase).forEach(System.out::println);
Stream<String> emptyStream = Stream.empty() // empty 메서드 사용하여 스트림 비우기.
Stream.ofNullable을 이용할 수 있다.
배열을 인수로 받는 정적 메서드 Arrays.stream을 이용해서 스트림을 만들 수 있다.
int[] numbers = {2, 3, 5, 7, 11, 13};
int sum = Arrays.stream(numbers).sum();
java.nio.file.Files의 많은 정적 메서드가 스트림을 반환한다.
함수에서 스트림을 만들 수 있는 두 정적 메서드 Stream.iterate와 Stream.generate를 제공한다. 이를 이용해서 무한 스트림
, 즉 고정된 컬렉션에서 고정된 크기로 스트림을 만들었던 것과는 달리 크기가 고정되지 않은 스트림을 만들 수 있다.
iterate와 generate에서 만든 스트림은 요청할 때마다 값을 만들기때문에 무제한으로 값을 계산할 수 있다. 이를 막고자 limit(n) 함수를 함께 연결하여 사용해준다.
iterate는 요청할 때마다 값을 생산할 수 있으며 끝이 없으므로 무한 스트림
을 만든다. 이러한 스트림을 언바운드 스트림
이라고도 한다. 이러한 특징이 스트림과 컬랙션의 가장 큰 차이다.
generate도 요구할 때 값을 계산하는 무한 스트림을 만들 수 있으나 생산된 각 값을 연속으로 계산하지는 않는다. generate는 Supplier를 인수로 받아서 새로운 값을 생산한다.