Java Enum은 언제 쓰면 좋을까?

yoondgu·2023년 2월 26일
1

Java 

목록 보기
17/18
post-thumbnail
post-custom-banner

우테코 5기 백엔드 과정 중 작성한 글입니다.
학습하는 과정인 만큼 잘못된 내용이 있다면 얼마든지 피드백 부탁드립니다 !!


레벨1 사다리 미션에는 "Java Enum을 적용하라"는 요구사항이 있었습니다.
처음에는 당장 Enum이 꼭 필요한 부분이 없는 것 같아 사용하지 않았지만,
1단계 리팩터링 과정과 2단계 추가 구현에서 Enum을 적용해보면서 장점을 체감할 수 있었습니다.

이를 계기로 Enum에 대해 학습해보고 미션에서의 경험을 토대로 Enum을 언제 쓰면 좋을지 저만의 기준을 정리해보고자 했습니다.

Java Enum

열거 타입(Enumeration Type)은 일정 개수의 상수 값을 정의한 다음, 그 외의 값은 허용하지 않는 타입입니다. Java에서는 1.5 버전에서부터 추가되었습니다.

  • Java Enum의 생성자는 제어자가 private으로 강제되기 때문에 컴파일 시점 이후에는 다른 상수 값이 추가될 위험이 없습니다.
  • 정수 상수를 열거하는 정수 열거 패턴에서의 단점을 해소하며 컴파일 타임의 타입 안전을 보장하고 표현력도 더 좋습니다.

(정수 열거 패턴의 예시)

		public static final int DAY_MONDAY = 1;
		public static final int DAY_TUESDAY = 2;
		public static final int DAY_WEDNESDAY = 3;
		public static final int DAY_THURSDAY = 4;
		public static final int DAY_FRIDAY = 5;
		public static final int DAY_SATURDAY = 6;
		public static final int DAY_SUNDAY = 7;
		
		public static final int MONTH_JANUARY = 1;
		public static final int MONTH_FEBURARY = 2;
		public static final int MONTH_MARCH = 3;
		// ... 생략
  • 정수 열거 패턴에서는 모두 같은 int 타입으로 선언하기 때문에 서로 다른 집합 간 == 연산을 해도 컴파일 에러를 확인할 수 없습니다. 하지만 각 집합 별로 Enum 클래스를 만들어 상수를 정의하면, 서로 다른 데이터 타입을 가지므로 타입 안전을 보장할 수 있습니다.
  • 정수 열거 패턴에서는 아래와 같이 변수명으로만 DAYMONTH의 집합을 구분할 수 있기 때문에 네이밍이 복잡해지고, 가독성도 떨어집니다. 그러나 Enum을 사용하면 클래스로 집합을 구분하면서도 각 상수마다 이름을 붙여줄 수 있어 보다 단순하고 명확하게 표현할 수 있습니다.

타입 안전, 타입 안정성이란?
이펙티브 자바에서 접하게 된 용어인데, 의도 하지 않은 타입의 변수 할당이나 형변환 등의 오류가 적다는 의미 정도로 이해하고 있습니다. 그래서 컴파일 시점의 체크가 중요한 것 아닐까? 생각이 듭니다.

언제 쓰면 좋을까?

위와 같은 특징을 고려했을 때 특히 Enum 적용이 효과적인 경우를 생각해보았습니다.

이펙티브 자바의 아이템 34에서는 필요한 원소를 컴파일타임에 다 알 수 있는 상수 집합이라면 항상 열거 타입을 사용하기를 제안합니다.
저는 이 문장에서 "필요한 원소를 컴파일타임에 다 알 수 있는" 보다도, "상수 집합"이라는 말에 초점을 맞추고 싶습니다.

가장 좋은 예시라 하기도 어렵고 아직 리팩터링 중인데다가 부족한 점이 많아 부끄럽지만, 이번 사다리 미션 코드와 함께 설명해보겠습니다.

1. 고정된 원소를 가지는 상태 집합을 표현하는 경우

아래는 사다리의 디딤대의 존재 유무 상태값에 대한 클래스입니다.

public enum StepPoint {

    EXIST,
    NONE;

    public static StepPoint convert(boolean existing) {
        if (existing) {
            return EXIST;
        }
        return NONE;
    }

    public boolean isContinuous(StepPoint other) {
        return (this == EXIST) && (other == EXIST);
    }
}

처음에는
상태가 여러 개가 있는 것도 아니고, 딱 두 개에 boolean 타입 변수 하나로도 표현할 수 있는 값을 굳이 Enum으로 표현해야 하나? 회의적이었지만,
미션 요구사항에 있는 만큼 적용해본 것이었습니다.

그런데 적용해보고 나니 장점이 보이기 시작했습니다.

	@Test
    @DisplayName("양쪽 디딤대 좌표값의 상태에 따라서 이동할 방향을 반환한다.")
    void should_ReturnDirection_When_GivenBothSideStepPoints() {
        assertThat(Direction.findDirection(NONE, NONE))
                .isEqualTo(DOWN);
        assertThat(Direction.findDirection(EXIST, NONE))
                .isEqualTo(LEFT);
        assertThat(Direction.findDirection(NONE, EXIST))
                .isEqualTo(RIGHT);
    }
  • 위 테스트코드에서 볼 수 있듯이, 각 상태에 EXIST, NONE이라는 이름을 부여할 수 있기 때문에 boolean 타입의 true, false로 표현하는 것보다 더 명확한 의미를 전달할 수 있게 되었습니다.
  • findDirection(true, false)와 같이 값을 전달해야 했다면, 해당 메서드를 모르는 사람은 true가 의미하는 바를 알아차리기 어려웠을 것입니다.

고정된 원소를 가지는 상태 집합이란?

  • 사다리 디딤대의 존재 유무사다리 디딤대의 상태라는 집합 안에 존재하는 두 가지 상수를, EXISTNONE으로 표현한 것입니다. 요일, 태양계와 같이 많은 Enum 활용의 예시를 비롯해 이러한 경우를 고정된 원소를 가지는 상태 집합을 가지는 값을 표현하는 경우라고 말해보면 어떨까요? (제가 마음대로 만들어본 말입니다..)
  • 일반 클래스 타입의 값은 데이터 타입 -> 값과 같은 관계를 가지지만 Enum 클래스의 값은 데이터 타입 -> 이름 -> 값으로 이루어집니다. 때문에 단순히 연관성 있는 상수 라기 보다는 이름을 명명할 수 있는, 몇 가지의 고정된 상태가 집합을 이루는 값을 표현할 때 Enum을 더욱 잘 활용할 수 있을 것입니다.
  • 예를 들어 저는 프리코스 때 로또 게임에 쓰이는 여러 매직 넘버를 모두 연관 있는 상수라고 판단하여 GameRule이라는 Enum 클래스에 정의했습니다. 하지만 로또 번호 개수로또 번호 범위는 사실상 상태 집합 관계에 있지는 않았기 때문에, 이 경우에는 Enum을 써서 얻는 효과보다 각 상수를 사용하는 클래스에서 정수 상수를 직접 선언해주었을 때 얻는 가독성 개선 효과가 더 컸습니다.

2. 하나의 기능이 각 상수마다 다르게 동작해야 할 경우

아래는 사다리에서 이동할 방향을 표현하는 클래스입니다.

public enum Direction {

    DOWN(NONE, NONE, index -> index),
    LEFT(EXIST, NONE, index -> index - 1),
    RIGHT(NONE, EXIST, index -> index + 1);

    private final StepPoint left;
    private final StepPoint right;
    private final Function<Integer, Integer> indexComputer;

    Direction(StepPoint left, StepPoint right, Function<Integer, Integer> indexComputer) {
        this.left = left;
        this.right = right;
        this.indexComputer = indexComputer;
    }

    public StepPoint getRightStepPoint() {
        return right;
    }

    public static Direction findDirection(StepPoint left, StepPoint right) {
        validateStepPoints(left, right);
        return Arrays.stream(values())
                .filter(destination -> (destination.left == left) && (destination.right == right))
                .findAny()
                .orElseThrow(() -> new IllegalArgumentException("해당하는 사다리 방향이 존재하지 않습니다."));
    }

    private static void validateStepPoints(StepPoint left, StepPoint right) {
        if (left.isContinuous(right)) {
            throw new IllegalArgumentException("연속된 디딤대 좌표값으로는 사다리 방향을 결정할 수 없습니다.");
        }
    }

    public int computeNextIndex(int index) {
        return indexComputer.apply(index);
    }
}
  • 일단 이 클래스는DOWN, LEFT, RIGHT 이라는 고정된 원소를 가지는 상태 집합을 표현합니다.
  • 그런데 현재 인덱스를 받아 다음 인덱스를 계산하는 기능이 상태에 따라 다르게 동작함을 알 수 있습니다.
  • Enum을 사용하면, 이를 각 상태에서 직접 관리하고 직접 수행하게 할 수 있습니다.
  • 만약 해당 기능을 외부에서 구현한다면, if문이나 switch문을 사용해야 하며 요구사항의 변화로 Direction의 상수가 추가될 때마다 해당 코드를 수정해주어야 했을 것입니다.

Enum 클래스가 기능을 가지는 것이 옳은가?

  • Enum은 상수를 저장하기 위한 클래스인데, 상수가 단순 값 저장 외에 기능을 가지는 것이 옳은가?에 대한 제 답변은 Enum은 단순 상수 저장 뿐만 아니라 상수의 성격을 가진 객체(싱글톤?)를 제공하는 클래스로 사용될 수 있다는 것입니다.
  • 따라서 적절한 상황에 활용한다면 Enum 또한 특정 기능을 가지는 객체를 만들어도 된다고 생각합니다.
  • 하지만 추가하려는 기능이 각 상수마다 다르게 동작하는 경우 또는 같은 동작을 공유하는 경우가 아니라면, 굳이 Enum 클래스에서 정의할 필요 없을 것입니다.

참고자료

이펙티브 자바 Effective Java 3/E - 아이템 34.int 상수 대신 열거 타입을 사용하라
자바의 정석 기초편 열거형
넥스트리소프트 홈 Java: enum의 뿌리를 찾아서...
우아한형제들 기술 블로그 Java Enum 활용기, 이동욱님

post-custom-banner

0개의 댓글