본문 바로가기

Programing/JVM(Java, Kotlin)

[Java] Effective Java : Item 36 EnumSet, 유스케이스 in Java 8

한국어판: 아이템 36. 비트 필드 대신 EnumSet을 사용하라 (6장)

Win32 윈도우 프로그래밍을 해본 사람은 알겠지만 비트 연산의 조합으로 만드는 여러가지 윈도우 API를 많이 사용한다.
예를 들면 AnimateWindow 함수는 3번째 인자(dwFlags)로 비트 연산자 OR(|)로 여러가지 조합된 상태를 입력을 할 수 있다.

// WinUser.h
#define AW_HOR_POSITIVE             0x00000001
#define AW_HOR_NEGATIVE             0x00000002
#define AW_VER_POSITIVE             0x00000004
#define AW_VER_NEGATIVE             0x00000008
#define AW_CENTER                   0x00000010
#define AW_HIDE                     0x00010000
#define AW_ACTIVATE                 0x00020000
#define AW_SLIDE                    0x00040000
#define AW_BLEND                    0x00080000

BOOL AnimateWindow(
  HWND  hWnd,	// A handle to the window to animate
  DWORD dwTime, // The time it takes to play the animation, in milliseconds.
  DWORD dwFlags	// The type of animation.
);

예를 들면 슬라이드 애니메이션을 2초동안 왼쪽에서 오른쪽으로 주고 싶다면 아래와 같이 쓸 수 있을 것이다.

HWND hWnd; // 대상은 이미 어디엔가 있다.
AnimateWindow(hWnd, 2000, AW_SLIDE | AW_HOR_POSITIVE);

반면 이펙티브 자바에서는 이런 비트 필드 대신 EnumSet을 쓰라고 idiom으로 만들었다.

자바 8에서는 비트 필드로 구현한 것과 EnumSet으로 구현한 것이 보여서 비교를 위해 정리를 해보았다.

비트 필드로 구현

스트림의 Spliterator 인터페이스에 선언된 Characteristic 상수값들은 enum이 아닌 int로 정의되어 있다.

package java.util;

public interface Spliterator<T> {
    // ..
    public static final int ORDERED    = 0x00000010;	// 16
    public static final int DISTINCT   = 0x00000001;	// 1
    public static final int SORTED     = 0x00000004;	// 4
    public static final int SIZED      = 0x00000040;	// 64
    public static final int NONNULL    = 0x00000100;	// 256
    public static final int IMMUTABLE  = 0x00000400;	// 1024
    public static final int CONCURRENT = 0x00001000;	// 4096
    public static final int SUBSIZED   = 0x00004000;	// 16384
    // ..

이것을 사용하는 구현체에서는 비트 OR(|)로 조합을 할 수 있다.

예로 IntStream에서 IntStream을 반환하는 iterate 메서드는 아래에 이렇게 구현하고 있다.

package java.util.stream;

public interface IntStream extends BaseStream<Integer, IntStream> {
	// ..
	public static IntStream iterate(final int seed, final IntUnaryOperator f) {
        Objects.requireNonNull(f);
        final PrimitiveIterator.OfInt iterator = new PrimitiveIterator.OfInt() {
            int t = seed;

            @Override
            public boolean hasNext() {
                return true;
            }

            @Override
            public int nextInt() {
                int v = t;
                t = f.applyAsInt(t);
                return v;
            }
        };
        return StreamSupport.intStream(Spliterators.spliteratorUnknownSize(
                iterator,
                Spliterator.ORDERED | Spliterator.IMMUTABLE | Spliterator.NONNULL), false);
    }

Spliterator.ORDERED | Spliterator.IMMUTABLE | Spliterator.NONNULL 의 결과는 1296 이다.

0000 0000 0001 0000: 0x00000010 (0016)
0000 0100 0000 0000: 0x00000400 (1024)
0000 0001 0000 0000: 0x00000100 (0256)
---------------
0000 0101 0001 0000: 0x00000510 (1296)

EnumSet 으로 구현

반면 Collector의 동반 클래스인 Collectors에서는 EnumSet으로 관리를 한다.

Collector 인터페이스에는 내부에 Characteristics 열거타입이 있다.

package java.util.stream;

public interface Collector<T, A, R> {
    // ..
    enum Characteristics {
        CONCURRENT,
        UNORDERED,
        IDENTITY_FINISH
    }
}

사용의 예시는 아래와 같다.

package java.util.stream;

public final class Collectors {

    static final Set<Collector.Characteristics> CH_CONCURRENT_ID
            = Collections.unmodifiableSet(EnumSet.of(Collector.Characteristics.CONCURRENT,
                                                     Collector.Characteristics.UNORDERED,
                                                     Collector.Characteristics.IDENTITY_FINISH));
    static final Set<Collector.Characteristics> CH_CONCURRENT_NOID
            = Collections.unmodifiableSet(EnumSet.of(Collector.Characteristics.CONCURRENT,
                                                     Collector.Characteristics.UNORDERED));
    static final Set<Collector.Characteristics> CH_ID
            = Collections.unmodifiableSet(EnumSet.of(Collector.Characteristics.IDENTITY_FINISH));
    static final Set<Collector.Characteristics> CH_UNORDERED_ID
            = Collections.unmodifiableSet(EnumSet.of(Collector.Characteristics.UNORDERED,
                                                     Collector.Characteristics.IDENTITY_FINISH));
    static final Set<Collector.Characteristics> CH_NOID = Collections.emptySet();
    // ..

왜 그랬을까?

그렇다면 Spliterator의 characteristics 는 EnumSet 대신 int 타입으로 characteristics을 구현한 이유는 무엇일까?
아마도 성능이나 가벼운 크기를 위해서 primitive 타입인 int을 쓴 것이 아닌가 추측해본다.

IntelliJ IDEA는 이런 매직 넘버에 org.intellij.lang.annotations.MagicConstant 애너테이션을 붙여준다.