[item35] Enum.ordinal 메서드 대신 인스턴스 필드를 사용하라

후추·2023년 4월 13일
0

들어가기

자바의 Enum 은 ordinal() 메서드를 제공한다.

공식 문서를 살펴보면 ordinal() 메서드는 열거된 상수의 순서를 반환한다는 것을 알 수 있다.

그러나 공식문서는 하나의 문장을 덧붙인다.

그것은 ordinal() 메서드를 프로그래머가 사용할 필요가 없다는 내용이다.

해당 메서드는 EnumSet, EnumMap 등 enum 기반의 자료구조에 사용할 목적으로 설계되었다고 쓰여있다.

Most programmers will have no use for this method. It is designed for use by sophisticated enum-based data structures, such as EnumSet and EnumMap. -oracle

Enum 에서 어떤 상수와 연관된 정수가 필요할 때 ordinal()을 사용하는 것이 왜 문제일까?

Enum 예제

ordinal() 사용하기

public enum Rank {
    ONE,
    TWO,
    THREE,
    FOUR,
    FIVE;

    public int numberOfRank() {
        return ordinal() + 1;
    }
}

Rank는 enum으로 각 순위에 맞는 상수들을 갖고 있다.

각 Rank 상수를 숫자로 나타낼 때 numberOfRank() 메서드를 이용할 수 있는데, 여기서 ordinal()이 사용된다.

이 코드는 세 가지 측면에서 변경에 취약하다.

상수 선언 순서

Rank의 상수 선언 순서가 바뀐다면 numberOfRank() 는 의도와 다르게 동작한다.

public enum Rank {
    TWO,
    ONE,
    THREE,
    FOUR,
    FIVE;

    public int numberOfRank() {
        return ordinal() + 1;
    }
}

상수 ONE, TWO의 선언 순서를 바꾼다면, ONE.numberOfRank(), TWO.numberOfRank() 값이 이전과 달라진다.

동일한 값의 상수 추가

Rank에 FIRST를 추가해보자.

FIRST는 ONE과 논리적으로 동일한 의미를 갖는다.

public enum Rank {
    ONE, FIRST,
    TWO,
    THREE,
    FOUR,
    FIVE;

    public int numberOfRank() {
        return ordinal() + 1;
    }
}

ONE과 FIRST는 논리적으로 의미가 같지만 numberOfRank()의 값은 다르게 된다.

또한 FIRST가 중간에 추가됨으로써, TWO 이하 상수들의 numberOfRank() 값도 달라지게 된다.

사용하지 않는 상수 삭제

특정 이유로 Rank의 FOUR 순위가 쓰이지 않게 되었다고 가정하자.

public enum Rank {
    ONE,
    TWO,
    THREE,
    EMPTY,
    FIVE;

    public int numberOfRank() {
        return ordinal() + 1;
    }
}

FOUR를 삭제하는 것은 가능하다.

다만 FOUR가 선언되었던 위치에 다른 상수가 반드시 들어가야만 한다.

FOUR의 위치를 비워두면 FIVE.numberOfRank() 값이 변하기 때문이다.

즉 사용하지 않을 값을 추가해야 코드가 의도대로 동작하게 된다.

인스턴스 필드 사용하기

위에서 보았듯 ordinal()은 변경에 취약하다.

만약 enum에서 각 상수와 연관된 정수가 필요하다면 인스턴스 필드에 원하는 정수를 저장하자.

public enum Rank {
	TWO(2),
    ONE(1), FIRST(1),
    THREE(3),
    FIVE(5);
    
    private final int number;

    Rank(final int number) {
        this.number = number;
    }

    public int getNumber() {
        return number;
    }
}

인스턴스 필드에 정수를 갖는 Rank는 상수 선언 순서, 동일한 값의 상수 추가, 사용하지 않는 상수 삭제 등의 변경에서 자유롭다.

정리

Enum을 사용할 때 상수의 정수를 얻기 위해 ordinal() 메서드를 사용하지 말자.

상수와 연관된 정수가 필요하다면 인스턴스 필드에 저장하자.

0개의 댓글