본문 바로가기
개발&프로그래밍

[JAVA] Stream의 map과 flatMap 차이

by 재아군 2024. 11. 1.

[JAVA] Stream의 map과 flatMap 차이

 

 

Java Stream API를 사용하다 보면 자주 마주치게 되는 map()flatMap() 메서드.

얼핏 비슷해 보이는 이 두 메서드의 차이점을 정확히 이해하기 위한 글입니다..

 

map() 메서드 이해하기

map()은 스트림의 각 요소를 변환하여 새로운 요소로 매핑하는 중간 연산입니다. 1:1 매핑이라고 생각하면 됩니다.

 

기본적인 map() 사용 예제

List<String> names = Arrays.asList("john", "jane", "mike");

// 모든 이름을 대문자로 변환
List<String> upperNames = names.stream()
    .map(String::toUpperCase)
    .collect(Collectors.toList());

System.out.println(upperNames); // [JOHN, JANE, MIKE]

 

객체 변환 예제

class User {
    private String name;
    private int age;

    // 생성자, getter, setter 생략
}

List<User> users = Arrays.asList(
    new User("john", 30),
    new User("jane", 25)
);

// User 객체에서 이름만 추출
List<String> userNames = users.stream()
    .map(User::getName)
    .collect(Collectors.toList());

System.out.println(userNames); // [john, jane]

 

 

flatMap() 메서드 이해하기

flatMap()은 스트림의 각 요소를 스트림으로 변환한 후, 모든 스트림을 하나의 스트림으로 평면화합니다.

1:N 매핑이라고 볼 수 있습니다.

 

 

기본적인 flatMap() 사용 예제

List<List<String>> nestedList = Arrays.asList(
    Arrays.asList("a", "b"),
    Arrays.asList("c", "d")
);

// 중첩 리스트를 단일 리스트로 평면화
List<String> flatList = nestedList.stream()
    .flatMap(Collection::stream)
    .collect(Collectors.toList());

System.out.println(flatList); // [a, b, c, d]

 

실전 예제

class Order {
    private List<Item> items;
    // 생성자, getter, setter 생략
}

class Item {
    private String name;
    private double price;
    // 생성자, getter, setter 생략
}

List<Order> orders = Arrays.asList(
    new Order(Arrays.asList(
        new Item("Apple", 1.0),
        new Item("Banana", 2.0)
    )),
    new Order(Arrays.asList(
        new Item("Orange", 3.0)
    ))
);

// 모든 주문에서 상품 이름만 추출
List<String> itemNames = orders.stream()
    .flatMap(order -> order.getItems().stream())
    .map(Item::getName)
    .collect(Collectors.toList());

System.out.println(itemNames); // [Apple, Banana, Orange]

 

 

 

map()과 flatMap()의 주요 차이점

 

 

1. 반환 타입

  • map(): 각 요소를 변환한 스트림 반환
  • flatMap(): 각 요소를 변환한 스트림들을 하나의 스트림으로 평면화하여 반환

 

2. 데이터 처리 방식

// map() 사용 - 중첩 구조 유지
List<String[]> result1 = words.stream()
    .map(word -> word.split(""))
    .collect(Collectors.toList());
// 결과: [[H, e, l, l, o], [W, o, r, l, d]]

// flatMap() 사용 - 평면화
List<String> result2 = words.stream()
    .flatMap(word -> Arrays.stream(word.split("")))
    .collect(Collectors.toList());
// 결과: [H, e, l, l, o, W, o, r, l, d]

 

 

활용 사례

1. 데이터 변환 (map 활용)

// 사용자 목록에서 이메일 도메인만 추출
List<String> domains = users.stream()
    .map(User::getEmail)
    .map(email -> email.split("@")[1])
    .distinct()
    .collect(Collectors.toList());

 

2. 중첩 데이터 처리 (flatMap 활용)

// 부서별 직원 목록에서 모든 직원의 이메일 추출
Map<String, List<Employee>> deptEmployees = getDepartmentEmployees();
List<String> allEmails = deptEmployees.values().stream()
    .flatMap(List::stream)
    .map(Employee::getEmail)
    .collect(Collectors.toList());

 

성능상 고려사항

  1. 메모리 사용
    • flatMap()은 중간 스트림을 생성하므로 메모리 사용량이 더 많을 수 있음
    • 대용량 데이터 처리 시 주의 필요
  2. 처리 속도
    • 단순 변환은 map()이 더 빠름
    • 복잡한 중첩 구조는 flatMap()이 효율적
// 대용량 데이터 처리 시 병렬 스트림 활용
List<String> result = complexList.parallelStream()
    .flatMap(data -> processData(data).stream())
    .collect(Collectors.toList());

 

 

정리

map() 사용이 적절한 경우

  • 단순 1:1 데이터 변환
  • 객체의 특정 필드 추출
  • 값 변환 작업

flatMap() 사용이 적절한 경우

  • 중첩된 컬렉션 처리
  • 복잡한 데이터 구조 평면화
  • 1:N 관계의 데이터 처리

 

map()flatMap()은 각각의 특성과 장단점이 있다. 상황에 맞는 적절한 메서드를 선택하여 사용하면 더 효율적인 스트림 처리가 가능하다. 특히 대용량 데이터를 다룰 때는 성능적인 측면도 고려하여 선택하는 것이 중요하다.

 

 

 

댓글