Map
Stream API에서 map과 flatMap은 특정 데이터를 선택하는 기능을 한다.
Map
map은 메서드를 파라미터로 받는다. 파라미터로 받은 메서드는 Stream 객체의 각 요소에 대해서 적용되어 메서드를 적용한 결과를 가진 새로운 Stream을 만들게 된다.
List<String> list = categories.stream() // Stream<Category>
.map(category -> category.getName()) // map(Category::getName), @return Stream<String>
.toList();// 위의 예제에서 카테고리 이름의 길이를 알고 싶을 때
List<Integer> list = category.stream()
.map(Category::getName)
.map(String::length)
.toList();위의 예제처럼 map은 여러개를 연결해, 계속해서 새로운 값으로 매핑할 수 있다. map(Category::getName)의 리턴 값은 Stream(String)이고, 이 객체에 대해서 다시 한번 map(String::length)로 매핑을 하면 Stream(Integer) 객체로 바뀐다.
Map에서 생길 수 있는 문제
List<String> list = Arrays.asList("hello", "world");만약 위의 코드에서 중복을 제거한 알파벳을 알고 싶다면 간단하게 아래와 같이 작성할 것이다.
list.stream()
.map(str -> str.split(""))
.distinct
.toList();그러나 map으로 전달한 람다식 str -> str.split("")은 String[]을 반환하고 따라서 리턴 값은 Stream<String[]>이다. 따라서 String[]에 대해서 중복을 제거하므로 출력은 아래와 같다.
[h, e, l, l, o]
[w, o, r, l, d]그렇다면 매핑한 String[]에 대해서 하나의 스트림으로 만드는 방법은 어떨까? Arrays.stream()은 배열을 입력 받아 하나의 스트림으로 만드는 메서드이다.
String[] strArray = {"hello", "world"};
Stream<String> strStream = Arrays.stream(strArray); // String[] 배열의 String을 stream으로 바뀜위의 방식을 이용해서 매핑해보자
list.stream()
.map(str -> str.split(""))
.map(Arrays::stream)
.distinct()
.toList();이 코드는 제대로 동작할 것 같지만 그렇지 않다.
map(str -> str.split(""))는 Stream<String[]> 타입을 반환하고, map(Arrays.stream)은 Stream<String>을 반환한다. 이제 두 개의 연산을 이어서 수행하게 되면, Stream<Stream<String> 타입이 반환되어 원래의 의도와는 달라지게 된다.
flatMap
위의 예제에서 잘못된 것을 flatMap을 이용하여 해결 할 수 있다.
map은 파라미터로 넘어온 람다식의 결과(String[]) 그 자체를 매핑하는 반면, flatMap의 경우 String[]의 내용물 로 매핑한다. 다시 말해서 여러 개로 나뉜 배열의 Stream을 하나의 Stream으로 바꾸는 것이다.
List<String> distinctList = list.stream()
.map(str -> str.split("")) // @return Stream<String[]>
.flatMap(word -> Arrays.stream(word)) // @return Stream<String>
//.flatMap(Arrays::stream)
.distinct()
.toList();정리하자면 flatMap은 stream의 각 값을 다른 stream으로 매핑한 후 하나로 합치는 역할을 수행한다.
| 메서드 | 반환 타입 | 설명 |
|---|---|---|
| map | Stream<Stream | 스트림으로 변환을 하지만 기존의 구조는 유지 |
| flatMap | Stream | 중첩된 스트림을 하나의 스트림으로 변환 |