비트 필드란
비트 필드(bit field): 비트별 OR를 사용해 여러 상수를 하나의 집합으로 모을 수 있는 집합.
비트별 연산을 사용해 합집합과 교집합 같은 집합 연산을 효율적으로 수행할 수 있다.
ex) text.applyStyles(STYLE_BOLD | STYLE_ITALIC);
열거한 값들이 주로 집합으로 사용될 경우, 이전에는 각 상수에 서로 다른 2의 거듭제곱 값을 할당한 정수 열거 패턴을 사용했다.
public class Text {
public static final int STYLE_BOLD = 1 << 0; // 1
public static final int STYLE_ITALIC = 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) { ... }
}
비트 필드의 단점
1. 정수 열거 상수의 단점을 지닌다.(아이템34)
2. 비트 필드 값이 그대로 출력되면 단순한 정수 열거 상수를 출력할 때보다 해석하기 어렵다(비트니까)
3. 비트 필드 하나에 녹아 있는 모든 원소를 순회하기도 까다롭다.
4. 최대 몇 비트가 필요한지를 API 작성 시 미리 예측하여 적절한 타입(보통 Int, long)을 선택해야 한다. (API 수정없이 비트 수[32or64]를 더 늘릴수 없기에)
대안: java.util.EnumSet 클래스 사용
- EnumSet은 열거 타입 상수의 값으로 구성된 집합을 효과적으로 표현해준다.
- Set 인터페이스를 완벽히 구현하며 타입 안전하고, 다른 어떤 Set 구현체와도 함께 사용할 수 있다.
- EnumSet 내부는 비트 벡터로 구현되었다. 원소가 총 64개 이하라면, EnumSet 전체를 long 변수 하나로 표현하여 비트 필드에 비견하는 성능을 보여준다.
- removeAll, retailAll 같은 대량 작업은 비트를 효율적으로 처리할 수 있는 산술 연산을 써서 구현했다.
위 예를 EnumSet을 사용해 수정하면 아래와 같다.
public class Text {
public enum Style {BOLD, ITALIC, UNDERLINE, STRIKETHROUGH}
// 어떤 Set을 넘겨도 되게 설정. 다른 Set 구현체에도 유연하게 처리하기 위해
public void applyStyles(Set<Style> styles) {
System.out.printf("Applying styles %s to text%n", Objects.requireNonNull(styles));
}
// 사용 예
public static void main(String[] args) {
Text text = new Text();
EnumSet<Style> bold = EnumSet.of(Style.BOLD, Style.ITALIC);
text.applyStyles(bold);
}
}
EnumSet의 유일한 단점이라면, 아직 자바11까지는 불변 EnumSet을 만들 수 없다는 것이다. 그때까지는 명확성과 성능을 희생해서 Collections.unmodifiableSet으로 EnumSet을 감싸 사용할 수 있다.
@Test
@DisplayName("unmodifiableSet은 불변으로 만들어 준다.")
public void unmodifiableSetTest() {
Set<Style> styles = Collections.unmodifiableSet(EnumSet.of(Style.BOLD));
Assertions.assertThrows(UnsupportedOperationException.class, () -> styles.add(Style.UNDERLINE));
}
UnmodifiableSet은 Collections의 정적 클래스로, Collections의 정적 클래스인 UnmodifiableCollection을 상속한다.
UnmodifiableColelctions은 변경 자체가 불가능하게 모두 예외를 던졌다
(+추가) EnumSet 내부 알아보기
EnumSet은 생성 시, 내부 열거 타입 상수의 개수에 따라 EnumSet을 상속받은 RegularEnumSet과 JumboEnumSet으로 반환한다.
Enum원소 64개 이하의 경우,RegularEnumSet에서 long으로 선언된 elements를 통해 다양한 메소드들이 비트연산으로 처리된다.
따라서 EnumSet이 비트 벡터로 구현되어 있고, 따라서 시 공간 복잡도가 효율적임을 알 수 있다.
* 위 글은 EffectiveJava 3/E 책 내용을 정리한 글로, 저작권 관련 문제가 있다면 댓글로 남겨주시면 즉각 삭제조치 하겠습니다.
'Reading > Effective Java' 카테고리의 다른 글
[Effective-Java] Item 38. 확장할 수 있는 열거 타입이 필요하면 인터페이스를 사용하라 (0) | 2021.10.31 |
---|---|
[Effective-Java] Item 37. ordinal 인덱싱 대신 EnumMap을 사용하라 (0) | 2021.10.31 |
[Effective-Java] Item 35. ordinal 메소드 대신 인스턴스 필드를 사용하라 (0) | 2021.10.31 |
[Effective-Java] Item 34. int 상수 대신 열거 타입을 사용하라 (0) | 2021.10.31 |
[Effective-Java] Item 25. 톱레벨 클래스는 한 파일에 하나만 담으라 (0) | 2021.10.31 |