언어/Effective Java
이펙티브 자바 Item 36- 비트 필드 대신 EnumSet을 사용하라
now0204
2024. 6. 7. 11:08
열거한 값들이 주로 집합으로 사용될 경우, 예전에는 상수에 서로 다른 2의 거듭제곱 값을 할당한 정수 열거 패턴을 사용해 왔다.
public class StyleWithBitField {
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
public void applyStyles(int styles) {
// styleWithBitField.applyStyles(STYLE_BOLD | STYLE_UNDERLINE);
}
}
- 위와 같이 비트별 OR를 사용해 여러 상수르 ㄹ하나의 집합으로 모을 수 있으며, 이 집합을 비트 필드라한다.
- 하지만, 비트 필드 또한 정수 열거 상수이므로, 정수 열거 상수의 단점을 그대로 지니며, 추가로 다른 문제도 가진다.
- 비트 필드 값이 그대로 출력되면, 단순한 정수 열거 상수를 출력할 때보다 해석하기 더 어렵다
- 비트 필드 하나에 녹아 있는 모든 원소를 순회하기 까다롭다.
- 최대 몇 비트가 필요한지 처음부터 예상하고 적절한 타입을 선택해야한다.
EnumSet
- 열거 타입 상수의 값으로 구성된 집합을 효과적으로 표현해준다.
- EnumSet클래스는 Set 인터페이스를 구현하여 어떤 Set 구현체와도 함께 사용할 수 있으며, 타입 안전하다.
- EnumSet 내부는 비트 벡터로 구현되어, 원소가 총 64개 이하라면, EnumSet 전체를 long 변수 하나로 표현하여 비트필드에 비견되는 성능을 보여준다.
- removeAll과 retainAll과 같은 대량 작업은 비트를 효율적으로 처리할 수 있는 산술 연산을 써서 구현했다.
import java.util.EnumSet;
import java.util.Set;
public class Text {
public void applyStyles(Set<Style> styles) {
// applyStyles(EnumSet.of(Style.BOLD, Style.UNDERLINE));
}
private enum Style {
BOLD, ITALIC, UNDERLINE, STRIKETHROUGH
}
단점
- EnumSet을 불변으로 반들 수 없다.
- 성능상 손해를 보게되지만, Collections.numodifiableSet()메서드를 통해 EnumSet을 불변으로 만들 수 있다.
비트 필드란?
메모리를 절약하기 위한 도구로 사용되었다. 비트 필드는 참-거짓 값을 여럿 결합해서 하나의 바이트로 만들어 메모리를 절약한다.
public class Car {
public final static int POWER_WINDOWS = 1;
public final static int POWER_LOCKS = 2;
public final static int SNOW_TIRES = 4;
public final static int STANDARD_TIRES = 8;
public final static int CRUISE_CONTROL = 16;
public final static int CD_PLAYER = 32;
public final static int AIR_CONDITIONING = 64;
private int options;
public Car( ) {
}
public void setOptions(final int options) {
this.options = options;
}
public int getOptions( ) {
return this.options;
}
}
- 각 비트마다 1혹은 0으로 옵션의 유무를 나타내게 된다.
- 위 필드에 저장된 값으로, 연산을 통해 car가 가진 특징들을 특정할 수 있는 것이다.
비트 어레이 vs 비트 필드
- 비트 어레이 : 정수 타입보다 크기가 크고, 정수로 인덱스된 많은 비트 집합을 저장하는데 사용
- 비트 필드 : 크기가 워드를 넘지 않는다.
비트 필드의 사용
- 비트 빌트 대체 : OR연산을 통해 나온 합이 옵션을 알려준다. 또한 XOR 연산을 이용해 옵션을 제거할 수도 있다.
- 비트 필드 비교 : AND 연산을 통해 두 비트연산자를 비교할 수 있다. 만약 결과가 1이라면 차는 해당 옵션을 가지고 있다.
EnumSet 이란?
EnumSet은 java.util 패키지에서 제공하는 Enum클래스와 함께 동작하는 구현체이다.
상수 그룹을 나타내는 목적으로 사용되는데, 예를 들어 봄,여름,가을,겨울 등 열거형을 나열하는 것으로 볼 수 있다.
특징
- enum값만 포함할 수 있고, 모든 값은 동일한 enum에 속해야한다.
- null 값을 추가하는 것은 허용하지 않는다.
- thread로부터 안전하지 않다.! 필요한 경우 외부에서 동기화해야한다.
- HashSet보다 훨씬 빠른 고성능 구현체다.
- iterator를 사용한 복제가 가능하다.
- EnumSet의 모든 메서드는 산술 비트 연산자로 구현되어 있다.
사용법
EnumSet static 메서드
- EnumSet.allOf() : 매개변수로 받는 열거형에 있는 모든 요소를 가져온다
- Enum.of() : 매개변수로 받은 해당 요소를 찾아서 넣는다.
- Enum.complementOf() : 매개변수로 받은 요소들만 빼고 넣는다.
- EnumSet.range() : 매개변수로 받는 두개의 구간안에 해당하는 요소를 넣는다.
- EnumSet.noneOf() : 매개변수로 받는 열거형을 비운 EnumSet을 반환한다.
enum Color {
RED, YELLOW, GREEN, BLUE, BLACK, WHITE
}
public class EnumDemo {
public static void main(String[] args) {
EnumSet<Color> set1, set2, set3, set4, set4_1, set5;
set1 = EnumSet.allOf(Color.class);
set2 = EnumSet.of(Color.RED, Color.GREEN, Color.BLUE);
set3 = EnumSet.complementOf(set2);
set4 = EnumSet.range(Color.YELLOW, Color.BLACK);
set5 = EnumSet.noneOf(Color.class);
set5.add(Color.BLACK);
set5.add(Color.BLUE);
set5.remove(Color.BLACK);
System.out.println("set1 = " + set1);
System.out.println("set2 = " + set2);
System.out.println("set3 = " + set3);
System.out.println("set4 = " + set4);
try {
set4_1 = EnumSet.range(Color.BLACK, Color.YELLOW);
System.out.println("set4_1 = " + set4_1);
} catch (IllegalArgumentException e) {
System.out.println("set4_1은 오류가 발생합니다 : " + e.getMessage());
}
System.out.println("set5 = " + set5);
}
}
참고자료
https://www.tcpschool.com/c/c_operator_bitwise
https://javabom.tistory.com/50