본문 바로가기
language/java

번외편) groupingBy()와 toMap()은 내부 아이디어가 매우 비슷하다.

by 죄니안죄니 2026. 6. 1.
반응형

groupingBy()와 toMap() 은 내부 아이디어가 매우 비슷하다.

 

둘 다

"Key를 만들어서 Map에 저장한다."

는 공통점이 있기 때문이다.


1. toMap()

 
Map<String, Integer> result =
    names.stream()
         .collect(Collectors.toMap(
             name -> name,
             String::length
         ));
 

흐름

Kim  -> key: Kim  value: 3
Jo   -> key: Jo   value: 2
Park -> key: Park value: 4
 

{
    Kim=3,
    Jo=2,
    Park=4
}
 

즉,

원소 하나 → Key 하나, Value 하나

이다.


2. groupingBy()

 
Map<Integer, List<String>> result =
    names.stream()
         .collect(Collectors.groupingBy(
             String::length
         ));
 

흐름

Kim  -> key: 3
Jo   -> key: 2
Park -> key: 4
Lee  -> key: 3
 

{
    2=[Jo],
    3=[Kim, Lee],
    4=[Park]
}
 

즉,

원소 하나 → Key 하나, Value는 List에 계속 추가

이다.


그림으로 보면

toMap

Kim
 │
 ├── key : Kim
 └── value : 3

↓

Map

Kim → 3
 

groupingBy

Kim
 │
 └── key : 3

↓

Map

3 → [Kim]
 

Lee
 │
 └── key : 3

↓

Map

3 → [Kim, Lee]
 

그래서 내부적으로도 비슷하다.

toMap

개념적으로

 
for (String name : names) {

    K key = ...;
    V value = ...;

    map.put(key, value);
}
 

groupingBy

개념적으로

 
for (String name : names) {

    K key = ...;

    map.computeIfAbsent(key,
        k -> new ArrayList<>());

    map.get(key).add(name);
}
 

즉,

groupingBy는 Map의 value가 Collection인 특수한 toMap이라고 생각해도 된다.


그래서 차이는 딱 하나다.

toMap

Map<K, V>
 

한 Key에는 Value가 하나만 있다.

Kim -> 3
Jo  -> 2
Park-> 4
 

중복 Key가 나오면

IllegalStateException
 

이 발생한다.


groupingBy

Map<K, List<V>>
 

한 Key에 여러 개가 들어갈 수 있다.

3 -> [Kim, Lee]
2 -> [Jo]
4 -> [Park]
 

중복 Key가 나오는 것이 정상 동작이다.


실무에서는 이렇게 기억하면 된다.

toMap()
──────────────
원소
  │
  ▼
(key, value)
  │
  ▼
Map<K, V>
 
groupingBy()
────────────────
원소
  │
  ▼
key
  │
  ▼
같은 key끼리 자동으로 List에 모음
  │
  ▼
Map<K, List<T>>
 

더 재미있는 관점

사실 groupingBy()는 아래와 같은 toMap()을 내부적으로 편하게 만든 API라고 생각해도 된다.

 
Collectors.toMap(
    User::getDepartment,
    user -> List.of(user),
    (list1, list2) -> {
        List<User> merged = new ArrayList<>(list1);
        merged.addAll(list2);
        return merged;
    }
)
 

이걸 너무 자주 쓰니까 JDK에서

 
Collectors.groupingBy(...)
 

라는 전용 Collector를 만들어 준 것이다.


한 줄로 정리

groupingBy()가 toMap()과 비슷해 보이는 이유는 둘 다 "원소를 Key 기준으로 Map에 저장한다"는 동일한 아이디어를 사용하기 때문이다.

차이는

  • toMap()은 Key → Value 하나
  • groupingBy()는 Key → Value(List/Set/Downstream 결과 여러 개)

 

 

 

같은 목적이라면 groupingBy()와 toMap()의 성능 차이는 거의 의미가 없다.
오히려 어떤 자료구조를 만들고 어떤 작업을 하는지가 더 중요하다.


1. toMap()

 
Map<String, Integer> result =
    names.stream()
         .collect(Collectors.toMap(
             name -> name,
             String::length
         ));
 

내부적으로는 거의

 
Map<String, Integer> map = new HashMap<>();

for (String name : names) {
    map.put(name, name.length());
}
 

와 비슷하다.

시간복잡도

N개의 원소

put() : O(1) 평균

전체 : O(N)
 

2. groupingBy()

 
Map<Integer, List<String>> result =
    names.stream()
         .collect(Collectors.groupingBy(
             String::length
         ));
 

내부적으로는

 
Map<Integer, List<String>> map = new HashMap<>();

for (String name : names) {

    int key = name.length();

    map.computeIfAbsent(key, k -> new ArrayList<>());

    map.get(key).add(name);
}
 

와 거의 같다.

시간복잡도

N개의 원소

computeIfAbsent() : O(1)
add() : O(1)

전체 : O(N)
 

3. 성능은 거의 비슷하다.

둘 다

원소 하나

↓

Hash 계산

↓

Map 조회

↓

저장
 

을 반복한다.

그래서

O(N)
 

이다.


4. 오히려 메모리 차이가 더 크다.

toMap()

Kim  -> 3
Jo   -> 2
Park -> 4
 
Map

Kim -> Integer
Jo  -> Integer
Park-> Integer
 

Value가 하나만 있다.


groupingBy()

Kim
Lee
Park
 

Map

3 -> List(Kim, Lee)
4 -> List(Park)
 

List 객체가 추가로 생성된다.

즉,

HashMap
+
ArrayList
+
ArrayList 내부 배열
 

등이 생긴다.


5. 다운스트림을 쓰면 더 효율적일 수도 있다.

예를 들어

비효율

 
users.stream()
     .collect(groupingBy(User::getDept))
 

Map<String, List<User>>
 

를 만들고

다시

 
list.size()
 

를 호출한다.


효율

 
users.stream()
     .collect(groupingBy(
         User::getDept,
         counting()
     ));
 

바로

Map<String, Long>
 

을 만든다.

List를 만들지 않는다.

메모리도 적게 쓰고 성능도 조금 더 좋다.


실무에서는

거의 항상

성능보다 목적에 맞는 Collector를 선택하는 것이 중요하다.

예를 들어

 
toMap()
 

을 억지로 써서

 
(key, List.of(value), merge)
 

처럼 만드는 것보다

 
groupingBy()
 

가 훨씬 읽기 쉽고 유지보수하기 좋다.


한 줄 정리

toMap()과 groupingBy()는 모두 평균 O(N)이며 내부적으로 HashMap 기반으로 동작한다. 성능 차이는 크지 않으며, groupingBy()는 List 생성 등의 추가 메모리 비용이 있지만 다운스트림 Collector(counting(), mapping() 등)를 활용하면 불필요한 Collection 생성을 줄여 오히려 더 효율적으로 사용할 수 있다.

반응형

'language > java' 카테고리의 다른 글

Reflection 원리  (0) 2026.06.16
번외편 - Java Comparator vs JavaScript sort()  (0) 2026.06.04
번외편) Typescript와 Generic 비교  (0) 2026.06.01
Factory Method Pattern과 Strategy Pattern 비교  (0) 2026.06.01
List.of에 대하여  (0) 2026.06.01

댓글