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

들어가며

비트 필드는 정수 열거 상수의 단점을 그대로 지니며 비트 필드 값이 그대로 출력되면 해석하기 어려운 문제를 안고있다. 더불어 최대 몇 비트가 필요한지를 API 작성 시 미리 예측하여 적절한 타입(int, long)을 선택해야 한다. API를 수정하지 않고서는 비트필드를 수정할 수 없다. 덫붙히자면 책에서는 순회는 어렵다고 나왔지만 실은 간단하다. 그렇다고 비트필드를 쓰라는 말은 절대 아니다.

1
2
3
4
5
while(bitField) {
if(bitField & 1)
do Something...
bitField>>=1;
}

비트 필드를 사용하고 싶다면 EnumSet을 사용하자. EnumSet은 Set 인터페이스를 완벽히 구현하며, 타입 안전하고 다른 Set 구현체와도 함께 사용할 수 있다. 하지만 EnumSet의 내부는 비트 벡터로 구현되어 있다. 원소가 64개 이하라면 즉, 대부분의 경우에 EnumSet 전체를 long 변수 하나로 표현하여 비트 필드에 비견되는 성능을 보여준다. removeAll이나 retainAll과 같은 대량 작업은 비트를 효율적으로 처리하는 산술 연산을 구현했다. 그러면서도 비트를 직접 다룰 때 겪는 오류들에서 해방된다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
/**
* Removes from this set all of its elements that are contained in the
* specified collection (optional operation). If the specified
* collection is also a set, this operation effectively modifies this
* set so that its value is the <i>asymmetric set difference</i> of
* the two sets.
*
* <p>This implementation determines which is the smaller of this set
* and the specified collection, by invoking the <tt>size</tt>
* method on each. If this set has fewer elements, then the
* implementation iterates over this set, checking each element
* returned by the iterator in turn to see if it is contained in
* the specified collection. If it is so contained, it is removed
* from this set with the iterator's <tt>remove</tt> method. If
* the specified collection has fewer elements, then the
* implementation iterates over the specified collection, removing
* from this set each element returned by the iterator, using this
* set's <tt>remove</tt> method.
*
* <p>Note that this implementation will throw an
* <tt>UnsupportedOperationException</tt> if the iterator returned by the
* <tt>iterator</tt> method does not implement the <tt>remove</tt> method.
*
* @param c collection containing elements to be removed from this set
* @return <tt>true</tt> if this set changed as a result of the call
* @throws UnsupportedOperationException if the <tt>removeAll</tt> operation
* is not supported by this set
* @throws ClassCastException if the class of an element of this set
* is incompatible with the specified collection
* (<a href="Collection.html#optional-restrictions">optional</a>)
* @throws NullPointerException if this set contains a null element and the
* specified collection does not permit null elements
* (<a href="Collection.html#optional-restrictions">optional</a>),
* or if the specified collection is null
* @see #remove(Object)
* @see #contains(Object)
*/
public boolean removeAll(Collection<?> c) {
Objects.requireNonNull(c);
boolean modified = false;

if (size() > c.size()) {
for (Iterator<?> i = c.iterator(); i.hasNext(); )
modified |= remove(i.next());
} else {
for (Iterator<?> i = iterator(); i.hasNext(); ) {
if (c.contains(i.next())) {
i.remove();
modified = true;
}
}
}
return modified;
}

EnumSet 인스턴스를 건네는 메소드의 파라미터는 인터페이스로 받는게 좋은 습관이다. 클라이언트가 다른 Set 구현체를 넘기더라도 처리가 가능해지기 때문이다.

1
2
public void someMethod(Set<someEnumClass> enumClass) { ... }
concrete.someMethod(EnumSet.of(someEnumClass.enum1, someEnumClass.enum3));
Author: Song Hayoung
Link: https://songhayoung.github.io/2020/08/13/Languages/Effective%20JAVA/item36/
Copyright Notice: All articles in this blog are licensed under CC BY-NC-SA 4.0 unless stating additionally.