Programming Language/Java / / 2025. 3. 26. 14:49

[EFFECTIVE JAVA] 비트 필드 대신 EnumSet을 사용하라

 


핵심 요약

 

열거 타입 상수의 집합을 표현할 때, 과거에 사용되던 비트 필드(Bit Field) 방식 대신 현대 자바에서는 EnumSet 을 사용하는 것이 훨씬 안전하고 효율적이며 가독성 높은 방법입니다.

EnumSet은 타입 안전성을 보장하고 비트 필드 수준의 성능을 제공하면서도 사용하기 쉽습니다.

 

 


비트 필드

 

정의

  • 열거한 값들을 주로 집합으로 사용할 경우, 각 상수에 서로 다른 2의 거듭제곱 값을 할당한 정수 열거 패턴을 사용
  • 비트별 OR(|) 연산을 통해 여러 상수를 하나의 집합(정수 값)으로 표현

 

예시 코드

public class FilePermissions {
    public static final int PERMISSION_READ    = 1 << 0; // 1 (읽기 권한)
    public static final int PERMISSION_WRITE   = 1 << 1; // 2 (쓰기 권한)
    public static final int PERMISSION_EXECUTE = 1 << 2; // 4 (실행 권한)

    public void setPermissions(int permissions) {...}
}

// 상수 정의 (정수 값 사용)
public class Text_BitField {
    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

    // 비트 필드(int)를 받는 메서드
    public void applyStyles(int styles) {
        System.out.println("Applying styles (bitmask): " + styles);
        // ... 스타일 적용 로직 ...
    }
}

// 사용 예시
text.applyStyles(STYLE_ITALIC | STYLE_UNDERLINE); // 값은 6
fp.setPermissions(STYLE_ITALIC | STYLE_UNDERLINE); // 오류없음

 

특징

  • 비트별 연산(OR, AND 등)을 사용하여 합집합, 교집합 등의 집합 연산을 효율적으로 수행 가능 (과거 관점).

 

장점

  • (과거 기준) 비트 연산을 통한 효율적인 집합 연산.

단점

  • 타입 안전성 부재
    • 단순 int 값이므로 관련 없는 값을 전달해도 컴파일 오류 미발생
  • 가독성 저하
    • 숫자만으로 스타일 정보 추론?
  • 순회 어려움
    • 집합에 포함된 개별 원소(스타일)를 순회하기 까다로움
  • 확장성 제한
    • 최대 원소 개수를 미리 예측하여 intlong 타입을 선택
    • 비트 수 확장할 때 API 변경이 필요
  • 오류 가능성
    • 비트 연산은 직접 다루기 까다롭고 오류가 발생 가능성




EnumSet

 

정의

  • 열거 타입(Enum) 상수들로 구성된 집합을 표현하기 위해 특별히 설계된 Set 인터페이스의 고성능 구현체
  • Set 인터페이스를 완벽히 구현, 타입 안전하고, 다른 어떤 Set 구현체와도 함께 사용 가능
  • 비트 필드를 대체하는 현대적이고 우수한 대안
  • java.util 패키지

 

예시 코드

import java.util.EnumSet;
import java.util.Set;
// 열거 타입 정의
public enum Style { BOLD, ITALIC, UNDERLINE, STRIKETHROUGH }

public class Text_EnumSet {
    // Set 인터페이스로 받는 것이 좋음
    public void applyStyles(Set<Style> styles) {
        System.out.println("Applying styles (EnumSet): " + styles);
        // ... 스타일 적용 로직 ...
    }
}

// 사용 예시
Text_EnumSet text = new Text_EnumSet();
text.applyStyles(EnumSet.of(Style.BOLD, Style.ITALIC)); // 출력: [BOLD, ITALIC]

 

특징

  • 내부적으로 비트 벡터를 사용하여 데이터를 저장
    • 비트 벡터는 주로 단일 long 변수
    • 매우 효율적
  • Set 인터페이스를 완벽하게 구현하며, 다른 Set 구현체와 함께 사용 가능
  • 다양한 정적 팩터리 메서드를 제공하여 편리한 인스턴스 생성
    • EnumSet.of(), EnumSet.allOf(), EnumSet.noneOf()

 

장점

  • 타입 안전성
    • 정의된 열거 타입의 상수만 포함
    • 컴파일 시점에 타입 오류를 방지
  • 가독성
    • 코드가 명확, 직관적
    • 출력 예시 [BOLD, ITALIC]
  • 고성능
    • 내부 구현 방식 덕분에 비트 필드에 비견될 만한 성능을 제공
    • 대량 작업도 비트 연산을 활용해 효율적으로 처리
      • removeAll, retainAll 등
  • 사용 편의성
    • 복잡한 비트 연산 내부 구현
    • 안전하고 편리한 집합 연산 제공
  • 유지보수 용이
    • 열거 타입 상수 추가시 EnumSet 코드는 대부분 수정 없이 작동

단점

  • (과거) 불변 EnumSet을 직접 생성 불가 (Java 9 이전)
    • 현재는 해결




핵심 정리 및 부가 정보

 

핵심 정리

 

결론적으로, 열거 타입 상수의 집합을 사용할 때는 명료성, 성능, 타입 안전성, 유지보수성 등 모든 면에서 비트 필드 대신 EnumSet을 사용하는 것이 압도적으로 좋습니다.

 


API 설계 팁:

 

// 구체화 EnumSet<E> -> 인터페이스 Set<E>
public void applyStyles(Set<Style> styles) // EnumSet ?
  • EnumSet<E> 대신 Set<E>을 사용하는 것이 더 유연하고 좋은 설계
  • 클라이언트가 다른 Set 구현체를 사용할 가능성

 

불변성(Immutability) 처리:

  • Collections.unmodifiableSet(EnumSet.of(...))
    • Java 9 이전에는 불변 EnumSet을 만드는 표준적인 방법이 없어서 사용
    • 원본 EnumSet이 변경되면 같이 변경될 수 있는 '뷰'를 제공할 뿐
    • 명확성, 성능 조금 희생
  • Set.copyOf(enumSet)
    • Java 10 이후 도입
    • 해당 EnumSet의 내용을 복사하여 진정한 불변 Set 을 생성
    • 더 안전하고, 특히 EnumSet의 경우 내부적으로 최적화되어 성능상 이점

  • 네이버 블로그 공유
  • 네이버 밴드 공유
  • 페이스북 공유
  • 카카오스토리 공유