EnumSet 에 대하여
- EnumSet 은 열거형 과 함께 사용하기 위한 Set 인터페이스의 특수 구현 중 하나이다
- EnumSet은 Set 인터페이스를 완벽히 구현하며, 타입 안전하고, 다른 Set 구현체와도 함께 사용할 수 있다
- EnumSet을 사용하면 해당 Set이 특정 enum 타입의 값만을 다룬다는 점이 명확히 표현되어 가독성을 높일 수 있다
- null 값 추가를 허용하지 않으며, 시도하면 NullPointerException 이 발생한다
- thread safe 하지 않으므로 필요한 경우 외부에서 동기화가 필요하다
- EnumSet의 내부는
비트 벡터로 구현되어 있으며 원소의 개수가 64개 이하라면 long 변수 하나로 표현할 수 있어서 비트필드에 비견되는 성능을 보여준다
비트벡터 : 중복되지 않는 정수 집합을 비트로 나타내는 방식 (자리에 해당하는 수에 0, 1 사용하여 표현)
-> 메모리 사용 크게 감소 가능
EnumSet 의 내부 noneOf 메소드를 살펴보면, 2가지 구현체로 구현되고 있다.
public static <E extends Enum<E>> EnumSet<E> noneOf(Class<E> elementType) {
Enum<?>[] universe = getUniverse(elementType);
if (universe == null)
throw new ClassCastException(elementType + " not an enum");
if (universe.length <= 64)
return new RegularEnumSet<>(elementType, universe);
else
return new JumboEnumSet<>(elementType, universe);
}
- RegularEnumSet
- RegularEnumSet은 단일 long을 사용하여 비트 벡터를 나타낸다
- long 의 각 비트는 enum 의 값을 나타냄 -> 열거형의 i번째 값은 i번째 비트에 저장되므로 값이 있는지 여부를 아는 것이 매우 용이하다
- long 은 64비트 데이터 유형 이므로 이 구현에서는 최대 64개의 요소를 저장 가능하다
- JumboEnumSet
- JumboEnumSet은 long 배열을 비트 벡터로 사용한다
- 이를 통해 64개 이상의 요소를 저장 가능하다
- 이는 RegularEnumSet 과 거의 유사하게 작동하지만 값이 저장된 배열 인덱스를 찾기 위해 몇 가지 추가 계산을 수행한다
- 배열의 첫 번째 long 요소는 enum 의 첫 번째 값 64개를 저장, 두 번째 요소는 다음 64개 등을 저장한다
- 당연히 RegularEnumSet 성능이 JumboEnumSet 보다 조금 더 좋다.
데이터를 찾기 위해 별도의 배열을 검색할 필요 없이 모든 데이터를 객체 내부에 저장하기 때문이다.
EnumSet 의 이점
- EnumSet의 모든 메서드는 산술 비트 단위 연산을 사용하여 구현된다
- 이러한 계산은 매우 빠르므로 모든 기본 작업이
상수 시간 내에 수행된다
- EnumSet을 HashSet 과 같은 다른 Set 구현 과 비교하면, 값이 예측 가능한 순서로 저장되고 각 계산에 대해 1비트만 검사하면 되기 때문에 일반적으로 더 빠르다.
- HashSet 과 달리 올바른 버킷을 찾기 위해 해시코드 를 계산할 필요가 없다 .
- 게다가 비트 벡터의 특성으로 인해 EnumSet은 매우 컴팩트하고 효율적이다.
- 따라서 메모리를 덜 사용하면서도 모든 이점을 누릴 수 있다.
결론
"Enum을 집합으로 표현하고 싶다면 그냥 HashSet에 넣어도 똑같은 거 아닌가?"
- 물론 기능은 같지만 성능적으로 큰 차이가 있다
- EnumSet의 모든 메서드는
비트 연산을 사용하고 있으며 64개 이하라면 하나의 long 비트만을 사용
- 각 계산에 대해 하나의 비트만 검사하는 EnumSet과 해시 코드를 계산해야 하는 HashSet을 비교한다면 당연히 EnumSet이 빠르다
- 즉, Enum 값을 집합으로 저장할 일이 있다면 가능한 EnumSet을 사용하는 것이 좋다