제 홈페이지의 모든 글은 anti-nhn license에 따릅니다.
2009년 02월 03일
EnumSet
EnumSet은 bit 필드를 대신하기 위해서 나온 것입니다.
다음과 같은 코드를 생각해봅시다.
public class Text{
public static final int STYLE_BOLD = 1;
public static final int STYLE_ITALIC = 2;
public static final int STYLE_UNDERLINE = 4;
public static final int STYLE_STRIKETHROUGH = 8;
// 인자는 STYLE_ 의 조합으로 이루어진 데이터.
public void applyStyles(int styles){ ... }
}
이 것을 사용하는 클라이언트 코드는
text.applyStyles(STYLE_BOLD | STYLE_ITALIC);
와 같이 됩니다.
일반적인 list나 set에 비해 bit 연산으로 처리하면 좋은 점은 속도가 월등히 빠르다는 것입니다.
그러나 여기에는 몇가지 문제 점들이 있습니다.
- type이 보장되지 않습니다. BOLD는 1이지만, 1이 반드시 BOLD의 의미로 쓰인다는 보장이 없습니다.
- 1에 연관되는 문제로 STYLE_과 같은 식의 prefix가 없으면 보기가 졸라 헤깔립니다. 또한 STYLE에 관련되지 않은 다른 종류의 상수들이 왕창 있을 경우 더욱 복잡해집니다. 예를 들어 FONT_SMALL = 1; 와 같은 것이 있다면, STYLE_BOLD와 값은 같지만 의미하는 바가 전혀 다르며, 전적으로 prefix에 의존해서 구분하는 수 밖에 없게 됩니다.
- 안전하지 못합니다. java의 constant는 값을 컴파일 시점에 값을 복사합니다. 따라서 api에서 코드를 바꿀 경우 클라이언트 코드가 정상적으로 작동하지 않습니다. ex>
public class Constants {
public static final String name = "홍길동";
}
public class AnotherClass{
String a = Constants.name;
}
이것은 컴파일 시점에
String a= "홍길동";
으로 바뀝니다. 따라서 Constants 클래스의 name값을 바꾸고 Constants만 다시 컴파일할 경우 AnotherClass의 a 는 여전히 "홍길동"입니다. - 값을 찍었을 때 무슨 뜻인지 직관적이지 않습니다. 예를들어, 3은 BOLD와 ITALIC의 의미가 됩니다.
- 값을 iteration 을 돌리기가 쉽지 않습니다. 비트 단위로 하나씩 자리수를 변경시켜가며 값이 있는지 없는 지 체크해야 합니다.
- 갯수가 너무 많을 경우 커버할 수 없습니다. int가 아니라 long으로 하더라도 그 범위를 넘어가는 것은 표현할 수 없습니다.
이에 대한 해결책으로 EnumSet 이 있습니다. 위의 예제를 enum과 EnumSet을 이용하여 다시 쓰면 아래와 같이 됩니다.
public class Text{
public enum Style{
BOLD, ITALIC, UNDERLINE, STRIKETHROUGH
}
//어떤 종류의 Set의 구현체이건 다 들어갈 수 있지만, EnumSet이 젤 좋다.
public void applyStyles(Set<Style> styles){ ... }
}
클라이언트 코드
text.applyStyles(EnumSet.of(Style.BOLD, Style.ITALIC));
이렇게 하면 위에서 열거한 모든 문제들이 해결됩니다.
- 타입이 안전해 졌습니다.
- enum 자체가 name space의 역할을 합니다.
- 상수와는 달리 동적으로 클라이언트 코드에 링크가 되기 때문에, api를 바꾸어도 클라이언트에 지장을 주지 않습니다.
- 이름이 주어지기 때문에 읽기가 쉽습니다.
- for(Style style : styles) 와 같이 iteration이 쉬워집니다.
- 갯수 제한도 없습니다.
위에서 열거한 점들은 bit 연산을 그냥 Set으로 바꿨을 때도 다 해결되는 문제들입니다. 그러면 EnumSet이 더 좋은 이유를 알아봅시다.
EnumSet은 contains 등의 메쏘드에서 Enum.ordinal() 등을 사용하여 비트 연산과 속도 차이가 거의 나지 않습니다. 따라서 HashSet보다 더 빠릅니다.
EnumSet의 static 메쏘드인 of를 사용하게 되면, 그 enum의 크기(element의 갯수)에 따라 JumboEnumSet과 RegularEnumSet (둘다 abstract class인 EnumSet을 상속받음) 중에 선택되어 객체를 생성합니다. (EnumSet.of() 는 factory method란 뜻이죠.)
클라이언트 코드를 HashSet으로 바꾸게 되면,
Set<Style> styles = new HashSet<Style>();
styles.add(Style.BOLD);
styles.add(Style.ITALIC);
text.applyStyles(styles);
와 같이 번거롭게 됩니다. 물론 좀 줄이면
text.applyStyles( new HashSet<Style>(){ { add(Style.BOLD); add(Style.ITALIC);}});
과 같이 되긴 합니다만, EnumSet.of 를 쓰는 코드에 비하면 역시나 번거롭기 짝이 없습니다.
# by | 2009/02/03 22:47 | 컴터질~ | 트랙백 | 덧글(6)





☞ 내 이글루에 이 글과 관련된 글 쓰기 (트랙백 보내기) [도움말]
비트 연산에 대한 것도 그렇구요..
오픈소스에 보면 비트연산을 해놓은 부분이 있기도 하더라구요..개인적으로 비트연산 코드는 본적이 거의 없어서.. 잘 모르거든요..
공부 좀 해봐야 겠네요.. ^^
감사합니다.~
위의 예제를 보면 2진수로 표현했을 때 (대략 4자리만..)
BOLD = 0001
ITALIC = 0010
UNDERLINE = 0100
STRIKE = 1000
입니다. 각각은 1개의 1과 나머지는 전부 0인 수로 표현이 되는거죠.
BOLD | ITALIC 과 같이 | 로 연결을 시키면 각 자리수 별로 or 처리가 됩니다. 즉 0011 이 되지요. (이게 10진수로는 3이죠. 10진수를 2진수로 바꿔서 자리수 별로 각각의 의미를 찾아내야 합니다. 가독성이 떨어진다는 의미가 이겁니다.)
넘겨 받은 인자를 arg라고 했을 때,
if( ( ITALIC & arg ) == ITALIC) 와 같이 체크하면 ITALIC이 있는지 알 수 있습니다. &는 and의 의미입니다.
일반적으로는 shift 연산을 쓰는 것 같더군요.
저두 전공자가 아니라 비트 연산은 잘 모릅니다만, 대략 아는 선에서 말씀드리면 이정돕니다.
(대학 다니면서 분명 배웠던 것들인데..군대 다녀오고 뇌 포맷이 된듯합니다 ㅠㅠ)
검색엔진에서도 저런 기법이 사용되는 것으로 알고 있는데..
가독성이 많이 떨어짐에도 불구하고 사용하는 이유는 말씀하신대로
속도에서 많은 이득이 있기 때문인가 보네요. ^^
감사합니다!
제게 익숙하지 않아서 마냥 가독성이 좋지 않은 것 같았는데
다시 보면 또 그렇지만도 않은거 같네요.
아무튼 굉장히 흥미가 가는 내용입니다.^^
똑똑한 사람들이 만든 것들 좀 더 찾아봐야겠습니다.