Baeldung 의 The Difference Between map() and flatMap() 문서를 번역(?)하면서 공부한 내용이라 언어만 한글일뿐 내용은 같습니다.
map()
과 flatMap()
API는 함수형 언어에서 유래됩니다. Java 8
에서는 Optional
, Stream
및 CompletableFuture
(약간 다른 이름으로)에서 찾을 수 있습니다.
Stream
은 일련의 객체를 나타내는 반면 Optional
은 존재하거나 없을 수 있는 값을 나타내는 클래스입니다. 다른 집계 작업 중에는 map()
및 flatMap()
메서드가 있습니다.
둘 다 동일한 리턴 타입을 가지고 있음에도 불구하고 상당히 다릅니다.
Stream
과 Optional
의 몇 가지 예를 분석하여 이러한 차이점을 설명합니다.
함수가 정확한 타입을 반환하는 경우 map() 메서드는 잘 동작합니다.
Optional<String> s = Optional.of("test");
assertEquals(Optional.of("TEST"), s.map(String::toUpperCase));
그러나 Optional
을 리턴하는 더 복잡한 함수가 제공될 수 있습니다.
이런 경우 map()
구현은 내부적으로 추가 래핑을 수행하므로 map()
을 사용하면 중첩 구조가 생성됩니다.
이 상황을 더 잘 이해하기 위해 다른 예를 살펴보겠습니다.
assertEquals(Optional.of(Optional.of("STRING)),
Optional.of("string").map(s -> Optional.of("STRING"))
);
보시다시피 중첩 구조인 Optional<Optional<String>>
으로 끝납니다.
동작하긴 하지만 사용하기가 꽤 번거롭고 추가적인 null
에 대해 안전하지 않으므로 flat한 구조를 유지하는 것이 좋습니다.
이 때 flatMap()
을 사용하면 도움이 됩니다.
assertEquals(Optional.of("STRING"),
Optional.of("string").flatMap(s -> Optional.of("STRING"))
);
Optional
과 비슷하게 동작합니다.
map()
메서드는 Stream
인스턴스에서 기본 시퀀스를 래핑하는 반면 flatMap()
메서드는 중첩된 Stream<Stream<R>>
구조를 피할 수 있습니다.
다음 코드에서는 map()
은 Stream
의 요소에 toUpperCase()
메서드를 적용한 결과로 구성된 Stream
을 생성합니다.
List<String> myList = Stream.of("a", "b")
.map(String::toUpperCase)
.collect(Collectors.toList());
assertEquals(asList("A", "B"), myList);
map()
은 이러한 간단한 경우에 꽤 잘 작동합니다.
그러나 중첩된 리스트와 같이 입력이 더 복잡할 경우에는 어떻게 될까요?
어떻게 동작하는지 봅시다.
List<List<String>> list = Arrays.asList(
Arrays.asList("a"),
Arrays.asList("b")
);
System.out.println(list);
// result: [[a], [b]]
이제 flatMap()
을 사용해봅시다.
System.out.println(list
.stream()
.flatMap(Collection::stream)
.toList());
// result: [a, b]
flatMap()
메서드는 먼저 입력 스트림을 문자열 스트림으로 평면화합니다.
이후에는 map()
메서드와 유사하게 동작합니다.
Java 8
은 원래 함수형 언어에서 사용되었던 map()
및 flatMap()
메서드를 사용할 수 있는 기회를 제공합니다.
Stream
및 Optional
에서 호출할 수 있습니다.
이러한 메서드는 제공된 매핑 기능을 적용하여 매핑된 객체를 가져오는데 도움이 됩니다.
참고문서
https://www.baeldung.com/java-difference-map-and-flatmap