티스토리 뷰
Item36에서는 비트 필드 대신 EnumSet을 사용하라는데 그전에 비트 필드는 무엇일까?
메모리를 직접 다루지 않는 자바에서는 비트 연산을 수행할 일이 많지 않다
특히나 고수준 API를 사용하는 웹 개발이라면 더욱 그렇다
'아 요런 것이 있구나'와 '이게 뭔데'는 하늘과 땅 차이니
세부 구현까지는 아니더라도 요런 것이 있구나 수준까지는 알아보자
비트 필드에 대한 상세한 설명이 담긴 좋은 글을 찾았다
깊게 알아보고 싶다면 꼭 읽어보자, 한 번에 빡 이해하기는 힘들것 같고 주기적으로 봐줘야겠다
컴퓨터 과학의 초기 시절에는 CPU, 메모리 같은 리소스가 상당히 부족했고
부족한 자원을 효율적으로 사용하기 위한 비트 필드 같은 요소를 memory-saving tools이라 한다
예시는 위에서 언급한 블로그를 참고했다, 자동차의 여러 옵션을 준다고 할 때 직관적인 구성으로 아래와 같이 작성할 수 있다
@Getter @Setter
@NoArgsConstructor
public class Car {
public boolean POWER_WINDOWS;
public boolean POWER_LOCKS;
public boolean SNOW_TIRES;
public boolean STANDARD_TIRES;
public boolean CRUISE_CONTROL;
public boolean CD_PLAYER;
public boolean AIR_CONDITIONING;
}
현재의 시점에서는 아무런 문제가 없는 코드지만 먼 옛날로 돌아가 메모리가 부족한 상황이라 치자
내가 원하는 것은 있는지 없는지만 판단하면 되니 1bit로 처리 가능한데 타입을 boolean으로 줬기 때문에 1byte가 할당된다
컴퓨터가 다룰 수 있는 최소 단위가 byte이기 때문에 단순히 있는지 없는지 판단할 때에도 byte로 다룰 수밖에 없다
1byte는 8bit이므로 위 예시에서 옵션 7개의 존재 여부를 나타내는데 충분하다
즉 1byte로 처리할 수 있는 문제를 7byte로 처리하고 있는 상황이다
지금이야 7byte면 이게 웬 떡이야 하고 간단히 넘겨버릴 상황이지만 옛날 옛적엔 byte도 절약해 사용했다
이를 해결하기 위해 조슈아가 구닥다리 기법이라 칭한 정수 열거 패턴부터 살펴보자
@NoArgsConstructor
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 void setOptions(final int options) {
this.options = options;
}
public int getOptions( ) {
return this.options;
}
}
setOptions에 값을 줄 때 어떻게 해야 하는가?, 비트 OR 연산을 때려준다
AIR_CONDITIONING까지 모든 옵션을 활성화하고 싶다면 모든 필드에 OR 연산 때려주면 된다
64, 32, 16, 8, 4, 2, 1 -> 11111111(2)로 표현이 가능하고
부호를 나타내는 맨 앞자리 Most Significant Bit, MSB를 제외하고 1byte로 7가지 옵션을 활성화 가능하다
한 가지 흥미로운 사실은 자바의 접근제어자, 필드 키워드들이 알고보니 비트 필드를 이용하고 있다는 점이다
비트 필드를 이용할 때 주의해야할 점으로 mutual exclusive가 지켜져야 한다는 것이 있는데
Modifier에서는 기본 생성자를 private으로 만들고 리플렉션을 사용해 생성하려 하면 에러를 터트리도록 만들어져 있다
만약 비트 필드를 사용해야 한다면 Modifier를 참고하자
package java.lang.reflect;
public class Modifier {
private Modifier() {
throw new AssertionError();
}
public static final int PUBLIC = 0x00000001;
public static final int PRIVATE = 0x00000002;
public static final int PROTECTED = 0x00000004;
public static final int STATIC = 0x00000008;
public static final int FINAL = 0x00000010;
public static final int SYNCHRONIZED = 0x00000020;
public static final int VOLATILE = 0x00000040;
public static final int TRANSIENT = 0x00000080;
public static final int NATIVE = 0x00000100;
public static final int INTERFACE = 0x00000200;
public static final int ABSTRACT = 0x00000400;
public static final int STRICT = 0x00000800;
}
PUBLIC = 1
PRIVATE = 2
PROTECTED = 4
STATIC = 8
FINAL = 16
SYNCHRONIZED = 32
VOLATILE = 64
TRANSIENT = 128
NATIVE = 256
INTERFACE = 512
ABSTRACT = 1024
STRICT = 2048
아하 비트 필드란 메모리 효율을 극대화하기 위함이로군?! 까지 이해가 됐다
비트 필드의 치명적인 단점으로는 7가지 옵션을 언급한 부분에서 눈치챌 수 있듯 확장성에 취약하다
기존에는 1byte로 해결되던 문제에서 옵션을 28개로 늘렸다면 어째야 하는가?
boolean에서 4byte를 가지고 있는 int 타입으로 바꿔야 한다
옵션을 53개로 늘렸다면? 8byte를 가지고 있는 long 타입으로 바꿔야 한다
확장이 결정된 순간 기존의 코드를 싹 다 갈아 치워야 한다
메모리 효율 극대화를 위해 전부 비트 필드로 깔아놨다면 아찔해지는 순간이다
또한 OOP 관점에서 비트 필드는 내부 구현을 노출하기 때문에 캡슐화를 깨기도 하고
하나의 options 필드로 옵션들을 관리하니 역할과 책임 측면에서도 옳지 않다
다음으로 EnumSet을 이용하는 현대적인 접근법의 best-practice를 확인해보자
public enum CarOption {
POWER_WINDOWS;
POWER_LOCKS;
SNOW_TIRES;
STANDARD_TIRES;
CRUISE_CONTROL;
CD_PLAYER;
AIR_CONDITIONING;
public void setOption(Set<CarOption> carOptions) {...}
}
이제는 자동차의 옵션을 지정하기 위해 간단하게
EnumSet.of(POWER_WINDOWS, POWER_LOCKS...)으로 구현체를 만들고 setOption()에 쏙 넣어주면 끝난다
EnumSet은 단순히 가독성만 뛰어나고 초당 40억 번의 연산이 되는 요즘의 4GHZ CPU만 믿고 까부는 녀석이 아니다
놀랍게도 최적화를 위해 내부적으로 비트 필드를 사용하고 있다 한다
따라서 성능 상 차이도 크지 않고, 가독성은 훨씬 뛰어나고 확장에도 뛰어나다
집합의 개수에 따라 64개를 기준으로 작거나 같으면 RegularEnumSet, 크면 JumboSet을 사용하도록 만들어져 있다
비트 필드 값을 교란시킬 수도 있는 리플렉션 방어 코드도 든든허이 짜여 있기 때문에
묻지도 따지지도 말고 EnumSet을 사용하자
'Java > Effective Java' 카테고리의 다른 글
[Item38] Enum 확장 시켜버리기 (0) | 2022.03.21 |
---|---|
[Item37] Collectors.groupingBy로 EnumMap 만들기 (0) | 2022.03.20 |
[Item35] ordinal 금지 (0) | 2022.03.15 |
[Item34] 항상 enum만 사용할 순 없으니 (0) | 2022.03.14 |
[Item33] Super Type Tokens (0) | 2022.03.13 |