Backend/Java

[Effective Java : Item 36] 비트 필드 대신 EnumSet을 사용하라

김세진 2024. 3. 5. 14:32
반응형

 

 

 

 

 

개요

 

열거한 값들이 집합으로 사용될 경우, 정수 열거 패턴(Item 34)을 사용할 수 있다. 여기에 비트 연산을 사용해 합집합과 교집합과 같은 집합 연산을 효율적으로 수행할 수 있는 비트 필드(bit field)를 활용하는 방법을 고려할 수 있다.

 

public class Text {
	public static final int STYLE_BOLD 			= 1 << 0; // 1
	public static final int STYLE_ETALIC 		= 1 << 1; // 2
	public static final int STYLE_UNDERLINE 	= 1 << 2; // 4
	public static final int STYLE_STRIKETHROUGH = 1 << 3; // 8

	// 매개변수 styles는 0개 이상의 STYLE_ 상수를 비트별 OR한 값이다.
	public void applyStyles(int styles) { ... }
}

 

이는 다음과 같이 사용할 수 있다.

 

text.applyStyles(STYLE_BOLD | STYLE_ETALIC);

 

하지만 이 비트 필드는 집합 연산의 효율성을 제외하면 아래와 같이 많은 단점을 추가로 지니고 있는 패턴이다.

 

단점

1. 정수 열거 상수의 단점을 그대로 지닌다.

2. 비트 필드 값만 가지고는 해석이 훨씬 어려워진다.

3. 비트 필드 하나에 녹아 있는 모든 원소를 순회하기 까다롭다.

4. 최대 몇 비트가 필요한지를 API작성 시 미리 예측하여 적절한 타입(보통 int나 long)을 선택해야 한다.

 

비트 필드는 아직도 간간히 사용되기는 하지만, 더 나은 EnumSet이라는 대안이 존재한다.

 

 


EnumSet

 

저자는 위의 비트 필드 방식은 비교적 구시대적인 방법이고, EnumSet은 이를 대체하는 현대적 기법이라고 설명한다. 다음 코드는 위의 비트 필드를 EnumSet 클래스로 변경한 코드이다.

 

class Text {
	public enum Style { BOLD, ITALIC, UNDERLINE, STRIKETHROUGH }

	// 어떤 Set을 넘겨도 되나, EnumSet이 가장 좋다.
    // EnumSet<Style>을 받아도 되지만, 유연함을 위해 이왕이면 인터페이스 타입을 받자.
	public void applyStyles(Set<Style> styles) { ... }
}

 

이는 다음과 같이 EnumSet의 정적 팩터리인 of 를 활용하여 사용할 수 있다.

 

text.applyStyles(EnumSet.of(Style.BOLD, Style.ITALIC));

 

장점

1. Set 인터페이스를 완벽히 구현하며, 다른 어떤 Set 구현체와도 함께 사용할 수 있다.

2. 타입 안전하다.

3. EnumSet의 내부는 비트 벡터로 구현되어 대부분의 경우(원소가 64개 이하인 경우) 비트 필드에 비견되는 성능을 지닌다.

4. removeAll, retainAll 같은 대량 작업 또한 효율적이다.

5. 비트를 직접 다룰 때 흔히 겪는 오류들에서 해방된다.

 

 


정리

 

1. 굳이 EnumSet 대신 비트 필드를 사용할 이유는 없으므로 EnumSet을 사용하자.

2. 유일한 단점이라면 불면 EnumSet을 선언할 수 없다는 것인데, 이는 Collections.unmodifiableSet으로 EnumSet을 감싸 사용하는 대안이 있다(명확성과 성능은 감소).

 

 

 

 

 

반응형