Effective Java : 아이템 36. 비트 필드 대신 EnumSet을 사용하라

    들어가기 전

    이 글은 이펙티브 자바 책을 공부하며 작성한 글입니다.


    아이템 36. 비트 필드 대신 EnumSet을 사용하라.

    • 비트 필드를 열거 타입으로 사용하면 다음 문제가 있음. 
      • 비트 필드 값이 그대로 출력되면 해석이 어려움. 
      • 비트 필드 하나가 출력 되었을 때, 어떤 원소가 포함되었는지 알기 어려움. (3 = 0011임, 그러면 12는?)
    • EnumSet을 사용하라.
      • EnumSet은 내부적으로 비트 벡터로 구현되어 있음.  
      • EnumSet을 생성하면, enum의 ordinal() 메서드를 이용해서 구해지는 값을 비트 값으로 표현한 형태로 저장됨.
      • EnumSet은 Set의 인터페이스 기능을 제공함. 내부적으로는 비트 연산을 하기 때문에 빠름. 
      • Enum을 이용하기 때문에 표현력도 좋음. 

    비트 필드 열거 패턴

    비트 필드 열거 패턴은 아래에서 볼 수 있다. "1 << 0"처럼 각 스타일에 대한 비트 값을 정의해둔다. 그리고 각 스타일을 조합해서 스타일을 적용할 때 applyStyles()를 호출한다.

    조합할 스타일을 비트 연산으로 처리하고 (예를 들면 STYLE_BOLD | STYLE_ITALIC), 그 결과를 applyStyles()에 전달해서 스타일을 적용하는 방식이다. 

    // 구닥다리 비트 열거
    public class TextWrong {
    
        // 각각은 비트 연산을 나타냄. 
        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
    
        // 매개변수 styles는 0개 이상의 STYLE_ 상수를 비트별 OR한 값이다. 
        public void applyStyles(int style) {
            System.out.println(style);
        }
    
        public static void main(String[] args) {
            TextWrong textWrong = new TextWrong();
            textWrong.applyStyles(STYLE_BOLD | STYLE_ITALIC); // 굳이 이런 비트 연산을 직접 할 필요가 있을까? 
        }
    }

    그런데 위 코드에서는 중대한 문제점이 있다. 아래 같은 문제점들이 있는데, 이런 이유 때문에 비트 열거 패턴을 사용하는 것보다는 EnumSet을 사용하는 것이 더 좋다. 

    • STYLE_BOLD | STYLE_ITALIC은 '3'이라는 값이 나온다. 디버깅 환경에서 '3'이라는 값이 나왔을 때, 3이 어떤 것을 의미하는지 바로 추론할 수 없음.
    • 다른 열거 패턴과 마찬가지로 같은 그룹의 열거 상수들을 순회할 방법이 마땅치 않다. 
    • 비트 연산을 직접 해야하기 때문에 자잘한 버그를 직접 처리해야한다. 

    EnumSet을 사용한 처리 방법

    위에서 빠른 속도로 연산을 해서 스타일을 적용하기 위해 비트 열거 패턴 + 비트 연산으로 처리했었다. 하지만 비트 열거 패턴은 표현력이 떨어지며, 복잡한 비트 연산을 직접 신경써야 한다는 단점이 있었다. 자바에서 enum이 나오면서 EnumSet이라는 클래스가 구현되었다. 비트 열거 패턴을 사용하기 보다는 EnumSet을 사용하는 것이 좋다.

    EnumSet은 다음 특징을 가진다.

    • EnumSet은 Enum을 원소로 가진다. 
    • EnumSet은 비트 벡터로 구현되어 있음. → 비트 연산만큼이나 빠름. 
    • 만들 때, 각 Enum의 ordinal()을 호출해서 그 값을 비트 값으로 가지도록 함. 
      • 아래 코드에서 EnumSet.of(ITALIC)이면 ITALIC.ordinal()의 값인 1을 비트로 가짐.
    • 64개 이하의 Enum을 가지는 경우, Long 타입 변수 하나만 이용해 64비트를 표현함. (1111.....1111, 총 64자리)
    • 아래 메서드를 이용해 비트 연산을 내부적으로 구현해줌.
      • of() : OR 연산(합집합)
      • retainAll() : AND 연산 (교집합)
      • removeAll(), complementOf() : NOT 연산
      • 등등

    EnumSet은 Set 인터페이스를 구현했으며, 구현 내부에서는 비트 벡터 연산을 통해 복잡한 비트 연산을 대신해준다. 또한 Enum을 사용했기 때문에 높은 표현력까지 잡았다.

    // EnumSet은 비트 벡터로 구현되어있고, 각 메서드는 비트 연산을 의미함.
    // 비트 연산이 구현되어 있어 속도가 빠르고, Enum으로 표현되어 표현력도 좋음. 
    public class TextGood {
    
        public enum Style {BOLD, ITALIC, UNDERLINE, STRIKETHROUGH;}
    
        public static EnumSet<Style> styleMap = EnumSet.allOf(Style.class);
    
    
        public void applyStyles(EnumSet<Style> style) {
            System.out.println(style);
        }
    
        public static void main(String[] args) {
            TextGood textWrong = new TextGood();
            textWrong.applyStyles(EnumSet.of(BOLD, ITALIC)); //  BOLD | ITALIC이라는 비트 연산 대신, EnumSet.of()라는 OR 연산을 사용하자.
        }
    }

    예를 들어 첫번째 비트 열거 패턴을 다음과 같이 처리해 줄 수 있다. 크게 차이 없어보일 수도 있지만, EnumSet의 표현력은 디버깅이나 로그를 찍었을 때 충분히 볼 수 있게 될 것이다. 

    댓글

    Designed by JB FACTORY