😏 개요
먼저 Enum에 대해 잘 모르면 아래 글을 먼저 읽어보길 바란다.
https://yeonyeon.tistory.com/171
Map<Enum, Integer>을 선언했는데 구현체로 뭘 사용할지 고민이 들었다.
검색 중에 EnumMap이라는게 있길래 정리해본다.
🤩 EnumMap이란?
- Enum 타입만 key로 사용 가능한 특별한 Map
- Array를 이용하기 때문에 성능적으로 우수
- 해싱 과정이 필요 없어 HashMap보다 빠름
- null을 key로 넣는 경우 NullPointerException 발생
- thread-safe하지 않음
🔻 EnumMap이 Array를 이용한다고?
new EnumMap<>(Enum클래스.class) 을 하면 아래 생성자가 호출된다.
public EnumMap(Class<K> keyType) {
this.keyType = keyType;
keyUniverse = getKeyUniverse(keyType);
vals = new Object[keyUniverse.length];
}
keyUniverse와 vals의 타입은 아래와 같다.
private transient K[] keyUniverse;
private transient Object[] vals;
🔻 EnumMap을 thread-safe하게 사용하고 싶다면?
아래와 같이 Collections.synchronizedMap()으로 감싸는 방법 등이 있다.
Collections.synchronizedMap(new EnumMap<EnumKey, V>(...));
🤔 EnumMap을 사용하는 이유
1. 성능이 우수하다.
EnumMap은 배열 형태로 이루어져있기 때문에 다른 Map에 비해 성능이 우수하다.
HashMap과 TreeMap의 차이점을 비교하자면 보통 아래 표를 꼽는다.
get / put / remove complexity가 HashMap의 경우에는 O(1)인 것으로 보인다.
그렇다면 HashMap도 EnumMap이랑 비슷하거나 더 좋은게 아닐까? 라는 의문이 들 수도 있다.
하지만 HashMap은 hashCode()를 사용해 키와 값을 저장하므로 해시 충돌 가능성이 존재한다.
EnumMap은 해시를 사용할 필요가 없으므로 충돌 가능성이 없다.
이에 대해서는 geeks for geeks에서 또는 Naver D2에 자세한 설명이 있다.
2. key를 제한할 수 있다.
key에 들어갈 값을 생성 시 정의하기 때문에 Enum 타입 외에는 key로 만들 수 없다.
(null을 key로 만드는 것도 막음!!)
아래처럼 LinkedHashMap과 EnumMap에 null을 보내보자.
HashMap에 null을 넣을 수 있음을 확인할 수 있다.
@Test
public void test() {
Map<Ranking, Integer> map1 = new EnumMap<>(Ranking.class);
Map<Ranking, Integer> map2 = new HashMap<>();
assertThatThrownBy(() -> map1.put(null, 3)).isInstanceOf(NullPointerException.class);
assertThatNoException().isThrownBy(() -> map2.put(null, 3));
}
😉 예제
1등부터 5등까지 상금을 담고 있는 Ranking이라는 Enum을 생성했다.
각 등수마다 몇 명이 존재하는지 저장하기 위해 아래와 같은 Map을 생성했다.
// LinkedHashMap
Map<Ranking, Integer> winningResult = new LinkedHashMap<>();
// EnumMap
Map<Ranking, Integer> winningResult = new EnumMap<>(Ranking.class);
LinkedHashMap으로 만든 경우에는 순서를 지정하기 위해 초기화 함수를 만들었다.
하지만 EnumMap은 생성 시 Ranking에 정의된 순서를 따른다.
private void initialMap() {
Ranking[] rankings = Ranking.values();
for (Ranking ranking : rankings) {
winningResult.put(ranking, 0);
}
}
❓ Map의 순서를 변경하고 싶은 경우
EnumMap은 우리가 순서를 정한 것도 아닌데 어떻게 저장될까 궁금해졌다.
EnumMap은 기본적으로 Enum에서 정의된 순서를 따른다.
keySet(), entrySet(), values()를 호출하면 이 순서를 확인할 수 있다.
만약 EnumMap를 마음대로 정렬해서 사용하고 싶다면 stream을 활용하자.
@Test
public void test() {
Map<Ranking, Integer> map = new EnumMap<>(Ranking.class);
map.put(Ranking.SECOND, 3);
map.put(Ranking.THIRD, 2);
map.put(Ranking.FIRST, 1);
// sort by Ranking에 저장된 상금
List<Map.Entry<Ranking, Integer>> entries = map.entrySet().stream()
.sorted((o1, o2) -> o1.getKey().getPrize() - o2.getKey().getPrize())
.collect(Collectors.toList());
for (Map.Entry<Ranking, Integer> entry : entries) {
System.out.println("Key: " + entry.getKey() + ", " + "Value: " + entry.getValue());
}
}
➕
명시적으로 보여주기 위해 o1.getKey().getPrize() 식으로 사용했는데 아래와 같이 리팩토링이 가능하다
// AS-IS
.sorted((o1, o2) -> o1.getKey().getPrize() - o2.getKey().getPrize())
// TO-DO
.sorted(Comparator.comparingInt(o -> o.getKey().getPrize()))
참고
'Develop > Java' 카테고리의 다른 글
[JUnit5] 중복되는 테스트 코드 줄이기 (0) | 2022.03.02 |
---|---|
[Java] 인스턴스화 방지를 위해 private 생성자 이용하기 (3) | 2022.02.26 |
[Java] ConcurrentModificationException 에러 (0) | 2022.02.24 |
[Java] Built-in Package (0) | 2022.02.17 |
[Java] toString() 파헤치기 (2) | 2022.02.15 |