본문 바로가기
Java

[java] EnumMap 정리

by ms727 2025. 7. 15.

EnumMap 파헤치기

회사에서 프로젝트를 진행하다가 EnumMap으로 상태관리하는 선임님의 코드를 보고 문득 파헤쳐보고 싶어졌습니다.

EnumMap개요

EnumMap이란?

  • Enum 타입의 상수를 키(Key)로 사용하기에 최적화된 자료구조
  • 내부적으로 배열(array)을 기반으로 구현되어 있어 일반 HashMap과는 차이가 존재합니다.

HashMap과 비교

  • HashMap은 키에 제약이 없지만, EnumMap는 오직 Enum 타입의 상수만을 키로 사용할 수 있습니다.
  • HashMap은 null을 키로 허용하지만 EnumMap은 허용하지 않습니다.
  • HashMap의 내부구조는 해시테이블로 구현되어있지만, EnumMap은 배열을 사용하여 구현됩니다.
  • 일반적으로 Enum을 키로 가진다면 EnumMap의 성능이 더 압도적입니다.
  • 둘다 스레드에 안전하지 않습니다.(Not Thread-safe)

Enum 주요 특징

가장 큰 특징은 Enum의 ordinal() 값을 활용하여 최적화된 자료구조를 구성하고 있는데, 이를 이용하면 해싱이나 해시 충돌처리 과정이 없어서 검색시 O(1)에 근접하는 속도를 보장하게끔 합니다.

EnumMap 사용법

Java24 기준 3가지 생성자가 존재합니다.

  • public EnumMap(Class<K> keyType)
  • public EnumMap(EnumMap<K, ? extends V> m)
  • public EnumMap(Map<K, ? extends V> m)

두 번째, 세 번째 생성자는 내부적으로 첫 번째 생성자의 구조를 따라가고 있어서, 첫 번째 생성자에 대해 좀 더 살펴보겠습니다.

내부 구현은 다음과 같이 이루어져있습니다.

public EnumMap(Class<K> keyType) {
    //keyType은 어떤 Enum 타입의 키를 사용하는지 나타내는 Class 객체입니다.
    this.keyType = keyType;
    //핵심 로직입니다.
    keyUniverse = getKeyUniverse(keyType);
    vals = new Object[keyUniverse.length];
}
// K타입이 반드시 Enum클래스를 상속받아야함을 명시하여 제약조건을 겁니다.
// 반환타입은 모든 열거형 상수들을 담고 있는 배열입니다.
private static <K extends Enum<K>> K[] getKeyUniverse(Class<K> keyType) {
    return SharedSecrets.getJavaLangAccess()
                                    .getEnumConstantsShared(keyType);
}
  • SharedSecrets: JDK 내부에서만 사용되는 클래스.
  • getJavaLangAccess: java.lang 패키지에 접근할 수 있는 내부 인터페이스의 인스턴스를 가져옴.
  • getEnumConstantsShared: 주어진 Enum클래스의 모든 상수들을 가져오는 기능을 수행함.

즉, JDK에서 제공하는 클래스의 메서드를 통해서 인자로 주어진 Enum 클래스의 상수 값들을 모두 가져와서 반환하고, keyUniverse에 저장합니다. 또한 그 크기를 vals에 저장합니다.


enum DayOfWeek {
    MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY
}

/*
    * EnumMap 기본 생성
    */
@Test
void enumGenerationTest() {
    EnumMap<DayOfWeek, Integer> enumMap = new EnumMap<>(DayOfWeek.class);
}

/*
    * EnumMap Copy 방법
    */
@Test
void enumGenerationCopyTest() {
    EnumMap<DayOfWeek, Integer> enumMap = new EnumMap<>(DayOfWeek.class);
    enumMap.put(DayOfWeek.MONDAY, 1);
    enumMap.put(DayOfWeek.TUESDAY, 2);
    enumMap.put(DayOfWeek.WEDNESDAY, 3);

    EnumMap<DayOfWeek, Integer> copiedEnumMap = new EnumMap<>(enumMap);

    copiedEnumMap.forEach((day, value) -> System.out.println(day + ": " + value));
}

이런식으로 생성이 가능합니다.

기본적인 메서드 사용법은 다음과 같습니다.

@Test
void enumMapCrudTest() {
    EnumMap<DayOfWeek, String> enumMap = new EnumMap<>(DayOfWeek.class);

    //Create
    enumMap.put(DayOfWeek.MONDAY, "월요일 업무");
    Assertions.assertEquals(enumMap.size(), 1);

    //Read
    String mondayTask = enumMap.get(DayOfWeek.MONDAY);
    Assertions.assertEquals(mondayTask, "월요일 업무");

    //Delete
    enumMap.remove(DayOfWeek.MONDAY);
    Assertions.assertEquals(enumMap.size(), 0);

    //containsKey
    enumMap.put(DayOfWeek.SATURDAY,"쉬기");
    Assertions.assertTrue(enumMap.containsKey(DayOfWeek.SATURDAY));
    Assertions.assertTrue(enumMap.containsKey(DayOfWeek.SUNDAY));

    //clear
    enumMap.clear();
    Assertions.assertEquals(enumMap.size(), 0);
}

사실 이런 사용법보다는 내부적으로 어떤 방법으로 구현되어서 이점을 챙기느냐입니다.

간단히 Put() 내부구현을 확인해보겠습니다.

public V put(K key, V value) {
    typeCheck(key);

    int index = key.ordinal();
    Object oldValue = vals[index];
    vals[index] = maskNull(value);
    if (oldValue == null)
        size++;
    return unmaskNull(oldValue);
}

여기서 핵심은 ordinal()입니다. Enum 클래스의 ordinal()함수를 사용하여 EnumMap 클래스의 내부 배열에서 직접 값을 추가하고 꺼냅니다. 때문에 ordinal()함수의 시간복잡도에 근접한 O(1)에 값을 가져오고 꺼낼 수 있습니다.

사실 ordinal()를 사용한 내부구현이 EnumMap의 전부라할정도로 내부구현에서 핵심로직을 담당하고 있습니다.

외에도 keySet(), values(), entrySet()과 같은 컬렉션뷰 메서드도 지원하니 확인해보면 좋을 것 같습니다.이 메서드들의 특징은 순서를 보장한다는 것입니다

@Test
void enumMapOrderedTest() {
    EnumMap<DayOfWeek, String> schedule = new EnumMap<>(DayOfWeek.class);
    schedule.put(DayOfWeek.MONDAY, "회의");
    schedule.put(DayOfWeek.WEDNESDAY, "발표");
    schedule.put(DayOfWeek.THURSDAY, "보고서 제출");
    // 화요일을 맨 마지막에 선언
    schedule.put(DayOfWeek.TUESDAY, "코딩");

    DayOfWeek[] expectedOrder = DayOfWeek.values();


    Iterator<DayOfWeek> keyIterator = schedule.keySet().iterator();
    int i = 0;
    while (keyIterator.hasNext()) {
        DayOfWeek currentDay = keyIterator.next();
        System.out.println(currentDay + " " + expectedOrder[i]);
        assertEquals(expectedOrder[i], currentDay);
        i++;
    }
}
// output

/*
* MONDAY MONDAY
* TUESDAY TUESDAY
* WEDNESDAY WEDNESDAY
* THURSDAY THURSDAY
*/

여기서 특이점은 keyIterator은 월,화,수,목, .. 이렇게 순회할 텐데,EnumMap은 데이터를 추가하는 순서와 관계없이, Enum 상수가 선언된 원래 순서(ordinal 값에 따른 순서)대로 데이터를 저장하고 컬렉션 뷰를 반환하는 것을 알 수 있습니다.

결론

이렇듯 EnumMap에 대해서 살펴봤습니다.

Enum을 키를 가져야하고 Map을 사용해야할 때 EnumMap을 사용해야겠다~ 고는 모두가 생각할 수 있지만, 왜?에 대한 해답을 갖고 가야합니다. 크게 2가지가 중요한 것으로 보입니다. 이 2가지를 언급하고 글을 마무리하겠습니다.

  1. EnumMap은 Enum 클래스의 메서드들을 사용하고 그에 맞춘 자료구조를 사용하여 탁월한 성능과 메모리 효율성 제공.
  2. 상수 선언대로 순서가 저장되어, 코드 예측 가능성을 높여줌.

'Java' 카테고리의 다른 글

[Java] 가상스레드 개념 파헤치기(Java24기준)  (2) 2025.07.18