-
Optional 에서 flatmap 과 mapBackEnd/자바 2022. 4. 7. 00:09
자꾸 헷갈리는 map vs flatMap, 제대로 정리해보자
자바에서 map과 flatMap은 스트림이나 Optional을 다룰 때 자주 쓰인다.
하지만 둘의 차이점은 처음 접하면 상당히 헷갈린다.
이 글에서는 Stream과 Optional을 중심으로 map과 flatMap의 개념, 용도, 차이를 예제와 함께 정리해보자.1. map과 flatMap, 가장 큰 차이점은?
간단하게 말하자면 이렇다:
map flatmap 결과 타입 값을 다른 값으로 변환 값을 스트림 또는 옵셔널로 감싸서 반환한 뒤, 그 껍데기를 벗겨서 평면화 중첩 제거 여부 중첩된 구조 유지 중첩된 구조 제거 1. map 만 사용
List<String> animals = List.of("cat", "dog"); List<String[]> result = animals.stream() .map(animal -> animal.split("")) .collect(Collectors.toList());
결과
[ ["c", "a", "t"], ["d", "o", "g"] ] // List<String[]>
map은 스트림의 각 요소를 배열로 바꿨지만, 배열 자체가 스트림에 들어가므로 중첩된 구조가 된다.
2. flatmap 사용
List<String> result = animals.stream() .map(animal -> animal.split("")) .flatMap(Arrays::stream) .collect(Collectors.toList());
결과
["c", "a", "t", "d", "o", "g"] // List<String>
flatMap은 각 String[]을 스트림으로 만든 다음, 전체 스트림을 평면화해준다.
즉, 여러 개의 작은 스트림을 하나의 스트림으로 합친다.3. Optional에서의 map vs flatMap
Optional에도 map과 flatMap이 있다.
차이는 Stream과 비슷하지만, 여긴 단일 값이기 때문에 더 단순한 구조다.Optional<String> name = Optional.of("animal"); Optional<Integer> length1 = name.map(String::length); // Optional<Integer> Optional<Optional<Integer>> length2 = name.map(s -> Optional.of(s.length())); // Optional<Optional<Integer>> Optional<Integer> length3 = name.flatMap(s -> Optional.of(s.length())); // Optional<Integer>
- map은 값을 감싼다 → 중첩 구조 생김
- flatMap은 껍데기를 벗기고 펼친다 → 중첩 제거
4. 실전 예제: 자동차 보험 찾기
public Optional<Insurance> nullsafeFindCheapeastInsurance(Optional<Person> person,Optional<Car> car){ return person.flatMap(p -> car.map(c->findCheapestIns(p,c))); } private Insurance findCheapestIns(Person p, Car c) { return null; }
이 코드는 Optional과 Optional가 모두 존재할 때만 보험을 찾는다.
중요한 건 findCheapestIns의 반환값이 Optional<Insurance>가 아니라 단순 객체라는 점이다:그렇기 때문에 map 안에서 flatMap을 쓰면 컴파일 오류가 발생한다:
private Insurance findCheapestIns(Person p, Car c) { return null; }
이유는 간단하다. findCheapestIns(p, c)가 Optional을 반환하지 않는데, flatMap은 반드시 Optional을 반환하는 함수를 요구하기 때문이다.
flatmap 와 map 의 메서드 시그니처
// map <U> Optional<U> map(Function<? super T, ? extends U> mapper) // flatMap <U> Optional<U> flatMap(Function<? super T, ? extends Optional<? extends U>> mapper)
- map: U를 반환하는 함수
- flatMap: Optional<U>를 반환하는 함수
5. 중첩 Optional 예제
Optional<Optional<Insurance>> insurance = person.map(p -> car.map(c -> findCheapestIns(p, c))); // Optional<Optional<Insurance>> 형태가 된다.
이럴 땐 flatMap을 써서 중첩을 없애주는 게 깔끔하다:
return person.flatMap(p -> car.map(c -> findCheapestIns(p, c)));
6. 함수가 Optional을 반환할 때 flatMap 사용
아래 함수는 Optional<Insurance>를 반환한다.
private Optional<Insurance> findCheapestIns(Person p) { return Optional.of(new Insurance()); }
이럴 땐 map을 쓰면 Optional<Optional<Insurance>>가 나오고,
flatMap을 써야 평면화가 된다.Optional<Insurance> result = person.flatMap(p -> findCheapestIns(p));
public Optional<Insurance> nullsafeFindCheapeastInsurance(Optional<Person> person,Optional<Car> car){ //테스트 Optional<Insurance> insurance1 = car.map(c -> findCheapestIns(c)); Optional<Insurance> insurance2 = person.flatMap(p -> findCheapestIns(p)); Optional<Optional<Insurance>> insurance = person.map(p -> car.map(c -> findCheapestIns(p, c))); //테스트 끝 return person.flatMap(p -> car.map(c->findCheapestIns(p,c))); } private Insurance findCheapestIns(Person p, Car c) { return null; } private Insurance findCheapestIns( Car c) { return null; } private Optional<Insurance> findCheapestIns(Person c) { return null; }
마무리 정리
map flatMap 단순 변환 o x 결과가 Optional 또는 Stream일 때 중첩 발생 평면화 (unwrap) 반환 함수 시그니처 T → U T → Optional<U> / Stream<U> - Stream의 flatMap: Stream<Stream<T>> → Stream<T>
- Optional의 flatMap: Optional<Optional<T>> → Optional<T>
처음엔 헷갈리지만, 핵심은 간단하다:
map은 그냥 감싸고, flatMap은 껍데기까지 벗긴다.
예제와 함께 반복해서 연습하면 자연스럽게 감이 잡힌다.
실무에서도 Optional, Stream, Flux 같은 다양한 상황에서 자주 마주치는 패턴이니 꼭 익혀두자.반응형'BackEnd > 자바' 카테고리의 다른 글
디폴트 메서드 - 모던 자바 인 액션 (0) 2022.04.10 Date - 모던 자바 인 액션 (0) 2022.04.09 Optional - 모던 자바 인 액션 (0) 2022.04.04 자바의 컬렉션 - 모던 자바 인 액션 (0) 2022.03.21 스트림의 병렬 처리 - 모던 자바 인 액션 (0) 2022.03.20