ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • Optional 에서 flatmap 과 map
    BackEnd/자바 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 같은 다양한 상황에서 자주 마주치는 패턴이니 꼭 익혀두자.

    반응형
Designed by Tistory.