Stream API의 map과 flatMap의 차이점을 설명하고, 각각의 활용 사례를 예시 코드와 함께 설명해주세요.
주제는 이렇다. 오늘은 map, flatMap뿐만 아니라 Stream API 자체에 대해 다루고자 한다.
컬렉션 데이터를 처리하기 위해 데이터 소스와 연결되어 영속된 데이터 흐름을 제공한다.
지연 처리: 중간 연산과 최종 연산
함수형 프로그래밍 스타일을 사용한다.(feat. 람다 표현식)
parallel() 또는 parallelStream()을 사용하면 데이터 처리를 병렬화할 수 있다.Stream API는 스트림에 있는 연산 기능을 말한다. 중간 연산과 최종 연산으로 나누어 진다.
조건에 맞는 요소만 선택한다.
List<Integer> numbers = Arrays.asList(1,2,3,4,5);
numbers.stream()
.filter(n -> n >2 )
.forEach(System.out::println);
--------------------------
3
4
5
데이터를 변환해서 새로운 형태로 변환한다. 예시에서는 모두 대문자로 바꾼다.
List<String> names = Arrays.asList("Alice", "Bob", "Christine");
names.stream()
.map(String::toUpperCase)
.forEach(System.out::println);
--------------------------
ALICE
BOB
CHRISTINE
중첩된 데이터 구조를 단일 평면 데이터 스트림으로 변환한다. 즉 여러 계층으로 중첩된 데이터를 풀어 플랫하게 만든다.
List<List<String>> nestedList = Arrays.asList(
Arrays.asList("A", "B"),
Arrays.asList("C", "D"),
Arrays.asList("E", "F")
);
List<String> flatList = nestedList.stream()
.flatMap(List::stream)
.collect(Collectors.toList());
System.out.println(flatList);
--------------------------
[A, B, C, D, E, F]
map()과 다른 점은 "입력 개수와 출력 개수가 반드시 같지는 않다"는 점이다.
map()의 예시에서 입력 데이터와 출력 데이터에서 다음 매핑 관계가 성립한다.
Alice -> ALICEBob -> BOBChristine -> CHRISTINE하나의 입력에 대해서 하나의 출력 결과가 나오는 1:1 관계이다.
flatMap()에서는 다음 매핑 관계가 성립한다.
("A", "B") -> "A", "B"("C", "D") -> "C", "D"("E", "F") -> "E", "F"nestedList는 중첩 리스트로 각각의 요소에 접근해도 여러 값으로 구성된 리스트이다.
nestedList에서의 stream()은 리스트 ("A" ,"B")라는 하나의 입력을 받아 "A", "B"로 나눈다.
즉 중첩 구조였던 nestedList를 일차원으로 폈다고 볼 수 있다.
collect()를 이용한다면 다음처럼 플랫해진 리스트를 따로 얻을 수 있다.
List<String> flatList = nestedList.stream()
.flatMap(List::stream)
.collect(Collectors.toList());
기준에 따라 정렬한다. 기준을 따로 주지 않아도 알아서 오름차순으로 정렬한다.
numbers.stream()
.sorted()
.forEach(System.out::println);
원하는 기준을 넣고 싶다면 Comparator나 Comparator.comparing()을 이용하자.
ComparatorList<Integer> numbers = Arrays.asList(5, 2, 8, 1, 3);
List<Integer> sorted = numbers.stream()
.sorted((a, b) -> b - a)
.collect(Collectors.toList());
--------------------------
[8, 5, 3, 2, 1]
Comparator.comparing()List<String> words = Arrays.asList("appleJuice", "banana", "apricot!", "cherry");
List<String> sorted = words.stream()
.sorted(Comparator.comparing(String::length)) //길이 기준 오름차순
.collect(Collectors.toList());
--------------------------
[appleJuice, apricot!, banana, cherry]
중복 요소를 제거한다.
List<String> names = Arrays.asList("Alice", "Alice", "Bob", "Cindy");
names.stream()
.distinct()
.forEach(System.out::println);
--------------------------
[Alice, Bob, Cindy]
처음 n개의 요소를 건너뛰고 연산한다.
List<Integer> numbers = Arrays.asList(1,2,3,4,5);
numbers.stream()
.skip(2)
.forEach(System.out::println);
--------------------------
3, 4, 5
스트림이 가져오는 요소 개수를 n개로 제한한다.
List<Integer> numbers = Arrays.asList(1,2,3,4,5);
numbers.stream()
.limit(3)
.forEach(System.out::println);
--------------------------
1, 2, 3
각 요소를 소비한다.
numbers.stream()
.forEach(System.out::println); // 메소드 레퍼런스 방식
numbers.stream()
.forEach(num -> {
System.out.println(num); // 람다 방식
}); // 각 요소 출력
결과를 특정 컬렉션으로 반환한다.
List<Integer> numbers = Arrays.asList(1, 2, 3);
// 각 요소를 제곱한 리스트로 반환
List<Integer> squares = numbers.stream()
.map(number -> number*number)
.collect(Collectors.toList());
// 단어를 키로, 길이를 값으로 하는 맵으로 반환
List<String> words = Arrays.asList("apple", "banana", "cherry");
Map<String, Integer> result = words.stream()
.collect(Collectors.toMap(word -> word, String::length));
모든 요소를 결합해서 하나의 결과를 생성한다.
reduce(초기값, 결합방식) 방식으로 사용한다.
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
//각 요소의 값 더하기
int sum = numbers.stream()
.reduce(0, (a,b) -> a + b); //초기값 0에서부터 두 값을 더함
//최댓값 구하기
int max = numbers.stream()
.reduce(Integer.MIN_VALUE, (a, b) -> a > b ? a : b); // Integer.MIN_VALUE로 초기화
일치하는 요소가 있는지 찾는다. 결과는 boolean으로 반환한다.
List<String> names = Arrays.asList("Alice", "Bob", "Cindy", "Daniel", "Edward", "Felilx");
// false
boolean hasLongName = names.stream()
.anyMatch(name -> name.length() > 10);
//true
boolean allShortNames = names.stream()
.allMatch(name -> name.length() < 10);
//true
boolean noNameStartWithZ = names.stream()
.noneMatch(name -> name.startsWith("z")); // "z" 로 시작하는 이름이 없는지 확인
조건을 만족하는 요소를 반환한다.
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
// 4 반환
Optional<Integer> first = numbers.stream()
.filter(n -> n > 3) // 3보다 큰 수만 필터링
.findFirst(); // 첫 번째 요소 반환
스트림에서 요소의 개수를 반환한다. filter()와 함께 사용해도 좋다.
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
int count = numbers.stream()
.count(); // 스트림의 요소 개수 세기
int even = numbers.stream()
.filter(n -> n % 2 == 0) // 짝수만 필터링
.count(); // 필터링된 요소의 개수
Stream API는 컬렉션 데이터를 처리하기 위한 메소드라고 했다.
이 컬렉션 데이터란, Collection interface를 구현하는 구현체 데이터를 말한다.
여기엔 List, Set 등이 포함된다.
다만 List와 Set만큼 자주 쓰는 Map은 포함되지 않는다!
Collection Interface 중 Set을 활용한다.
Map에서 나올 만한 Set은 key set, value set, entry set이다. 모두 Map에 내장된 메소드를 이용해 구할 수 있다. 이 Set 형태의 데이터들로 Stream API를 쓰면 된다.
코드잇 스프린트 강의 노트
양성욱 님의 포스팅: [Java] Stream API