스트림(내부 반복)을 이용하여 외부 반복에서는 얻을 수 없는 병렬 처리, 데이터 처리의 추상화를 통해 비즈니스에 집중할 수 있습니다.
필터링? 스트림의 요소
를 선택
하는 방법
Predicate를 받아 일치하는 모든 요소를 포함하는 스트림을 반환
Menu menu = new Menu();
List<Dish> vegetarianMenu = menu.dishes.stream()
.filter(Dish::isVegetarian) // 채식 요리인지 확인
.collect(Collectors.toList());
List<Integer> numbers = Arrays.asList(1, 2 , 1 ,3, 4, 2);
numbers.stream()
.filter(i -> i % 2 == 0)
.distinct()
.forEach(System.out::println);
Java9에서 처음으로 나온 기능입니다.
스트림의 요소를 선택/스킵하는 다양한 방법을 알아봅니다.
(ex 처음 몇 개의 요소를 무시, 특정 크기로 스트림을 줄이기..)
만약 칼로리순으로 정렬이 되어있다면 takwhile()을 사용합니다.
조건에 대하여 '참'이 아닌 경우 멈춥니다.
List<Dish> sliceMenu1
= specialMenu.stream()
.takeWhile(dish -> dish.getCalories() < 320) // 조건에 대하여 참이 아닌 경우 정지
.collect(Collectors.toList());
taskWhile의 반대를 수행합니다.
처음으로 '거짓'이 되는 경우의 이전까지 모두 버립니다.
만약 요소가 무한한 요소인 경우 메모리 공간을 고려해야 합니다.
List<Dish> sliceMenu2
= specialMenu.stream()
.dropWhile(dish -> dish.getCalories() < 320)
.collect(Collectors.toList());
Predicaate와 일치하는 처음 세 요소를 선택한 다음 즉시
반환합니다.
주의. 정렬되지 않은 Stream(Set..)에도 limit을 사용이 가능하며
이러한 경우에도 limit의 결과는 정렬이 적용이 되어있지 않음
스트림의 처음 n개 요소 건너뛰기를 skip(n)으로 지원합니다.
limit(n)과 반대의 기능을 합니다.
특정 객체에서 특정 데이터를 선택하는 작업, map
과 flatMap
을 이용하여 제공
기존의 값을 수정한다
라는 개념보다는 새로운 버전을 만든다
라는 개념에 가까우므로 매핑
이라는 단어를 사용합니다.
menu.dishes.stream()
.map(Dish::getCalories)
.collect(Collectors.toList());
menu.dishes.stream()
.map(Dish::getCalories)
.map(Integer::byteValue)
.collect(Collectors.toList());
menu.dishes.stream()
.map(dish -> dish.getName().split(""))
.distinct()
.collect(Collectors.toList());
map()은 split()의 반환값을 반환합니다.
stream().map(dish -> dish.getName().split(""))
String[]보다는'h', 'e'같은 알파벳(String)을 원합니다.
이러한 문제를 flatMap
을 이용하여 해결합니다.
Stream<String> streamOfWords = Arrays.stream(arrayOfWords);
streamOfWords
.map(word -> word.split("")) <- 각 단어를 자른 배열로 변환
.flatmap(Arrays::stream) <- 별도의 스트림으로 생성
.distinct()
.collect(Collectors.toList());
여러가지 예시
// 제곱근 구하기
List<Integer> ints = Arrays.asList(1,2,3,4,5);
ints.stream()
.map(num -> num * num)
.collect(Collectors.toList());
// 숫자 쌍 구하기
List<Integer> ints1 = List.of(1, 2, 3);
List<Integer> ints2 = List.of(3, 4);
List<int[]> result = ints1.stream()
.flatMap(i ->
ints2.stream().map(j -> new int[]{i, j}))
.collect(Collectors.toList());
// 숫자 쌍에서 합이 3으로 나누어떨어지는 것
List<int[]> result2 = ints1.stream()
.flatMap(i ->
ints2.stream()
.filter(j -> (i + j) % 3 == 0)
.map(j -> new int[]{i, j}))
.collect(Collectors.toList());
특정 속성이 데이터 집합에 있는지 여부를 검사
allMatch, anyMatch, noneMatch, findFirst, findAny
다양한 유틸리티 제공
anyMatch
는 boolean을 반환하므로 최종 연산
// 적어도 한 요소와 일치하는가?
if(menu.dishes.stream().anyMatch(Dish::isVegetarian)) {
System.out.println("menu is vegetarian friendly");
}
NOENMATCH
allMatch와 반대 연산, 주어진 프레디케이트와 일치하는 요소가 없는지 확인
boolean isHealthy = menu.dishes
.stream().noneMatch(d -> d.getCalories() >= 1000);
anyMatch, allMatch, noneMatch는 쇼트서킷 기법, &&, ||같은 연산을 활용
쇼트 서킷?
표현식에서 하나라도 조건에 맞는 결과가 나온다면 나머지 표현식의 결과와 상관없이 전체 결과를 반환하는 것,
즉 allMatch, noneMatch, findFirst같이 원하는 것을 만나면 바로 반환하는 것
findAny
현재 스트림에서 임의의 요소를 반환
Optional<Dish> dish = menu.dishes
.stream()
.filter(Dish::isVegetarian)
.findAny();
Optional
은 값의 존재나 부재 여부를 표현하는 컨테이너 클래스
아래 예제는 아무 것도 반환하지 않을 수 있습니다.
null은 쉽게 에러를 일으킬 수 있으므로 Optional이 탄생
Optional<Dish> dish = menu.dishes
.stream()
.filter(Dish::isVegetarian)
.findAny(); // Optional 반환
menu.dishes
.stream()
.filter(Dish::isVegetarian)
.findAny()
.ifPresent(vegetarianDish -> System.out.println(vegetarianDish.getName())); // 값이 존재하면 출력, 없다면 아무 일도 일어나지 않음
리스트 또는 정렬된 연속 데이터로부터 생성된 스트림 처럼 일부 스트림은 논리적인 아이템 순서가 정해져 있을 수 있습니다.
3으로 나누어떨어지는 첫 번째 제곱값 반환하기
// 첫 번째 요소 찾기
List<Integer> someNumbers = Arrays.asList(1,2,3,4,5);
Optional<Integer> firstSquareDivisibleByThere =
someNumbers.stream()
.map(n -> n * n)
.filter(n -> n % 3 == 0)
.findFirst();
findAny vs findFirst
병렬 실행에서는 첫 번째 요소를 찾기 힘들다.
요소의 반환 순서가 상관없다면 병렬 스트림에서는 제약이 적은 findAny 사용
"메뉴의 모든 칼로리 합계 구하기", "메뉴에서 칼로리가 가장 높은 요리" 같이
스트림 요소를 조합해서 더 복잡한 질의를 표현하는 방법을 알아봅니다.
이러한 질의(모든 칼로리의 합계, 칼로리가 가장 높은 요리)는 Integer같은 결과가 나올 때까지 스트림의 모든 요소를 반복적으로 처리해야 합니다.
이러한 연산을 리듀싱 연산이라고 합니다.
(함수형 프로그래밍 용어로는 fold라고 부른다)
for-each를 통한 합산
int sum = 0;
for(int x : numbers) {
sum += x;
}
두가지 파라미터가 사용
reduce()를 이용하여 추상화하기
for-each와 같은 동작을 하며 코드도 매우 간단해졌습니다.
ver1
int sumInt = numbers.stream().reduce(0, (a, b) -> a + b);
ver2
int sumInt = numbers.stream().reduce(0, Integer::sum);
초깃값이 없는 reduce
스트림에 아무 요소도 없는 상황이 있다면 합계를 반환할 수 없으므로 null보다는 Optional을 이용하여 반환합니다.
초깃값이 없는 오버로드된 reduce를 사용한다면 Optional을 반환
Optional<Integer> sumInt2 = numbers.stream().reduce(Integer::sum);
최댓값, 최솟값
reduce는 스트림의 모든 요소에 동일하게 반복적인 처리를 한다는 것을 활용
Integer max = numbers.stream().reduce(0, Integer::max);
map, filter 등은 입력 스트림에서 각 요소를 받아 0 또는 결과를 출력 스트림으로 보냅니다. 따라서 보통은 상태를 가지지 않는 연산
reduce, sum, max같은 연산은 결과를 누적할 내부 상태가 필요합니다. 스트림에서 처리하는 요소 수와 관계없이 내부 상태의 크기는 한정
sorted, distinct같은 연산은 모든 요소가 버퍼에 추가되어 있어야 합니다. 연산을 수행하는 데 필요한 저장소 크기가 정해지지 않음
따라서 데이터 스트림의 크기가 무한이면 문제가 생길 수 있습니다