38. 확장할 수 있는 열거 타입이 필요하면 인터페이스를 사용하라

열거(enum)타입이란?

고정된 상수들의 집합을 표현하는 특별한 종류의 클래스

열거타입의 특징

  • 고정된 상수 집합을 표현할 때 사용한다

  • 다른 클래스를 상속할 수 없다

  • 일반적으로 열거 타입을 확장하는 것은 좋지 않다

열거타입을 확장하려면? 인터페이스를 이용하자!

예시

공통 기능을 담은 인터페이스

public interface Operation {
    double apply(double x, double y);
}

기본 연산 기능을 담은 열거 타입

public enum BasicOperation implements Operation {
    PLUS("+") {
        public double apply(double x, double y) { return x + y; }
    },
    MINUS("-") {
        public double apply(double x, double y) { return x - y; }
    },
    TIMES("*") {
        public double apply(double x, double y) { return x * y; }
    },
    DIVIDE("/") {
        public double apply(double x, double y) { return x / y; }
    };

    private final String symbol;

    BasicOperation(String symbol) {
        this.symbol = symbol;
    }

    @Override public String toString() {
        return symbol;
    }
}

확장 연산 기능을 담은 열거 타입

public enum ExtendedOperation implements Operation {
   EXP("^") {
       public double apply(double x, double y) {
           return Math.pow(x, y);
       }
   },
   REMAINDER("%") {
       public double apply(double x, double y) {
           return x % y;
       }
   };
   private final String symbol;
   ExtendedOperation(String symbol) {
       this.symbol = symbol;
   }
   @Override public String toString() {
       return symbol;
   }
}

사용 방법

📌 방법 1 : 클래스 타입 전달 (한정적 타입 매개변수 사용)

public static void main(String[] args) {
    double x = Double.parseDouble(args[0]);
    double y = Double.parseDouble(args[1]);
    test(ExtendedOperation.class, x, y);
}
private static <T extends Enum<T> & Operation> void test(
        Class<T> opEnumType, double x, double y) {
    for (Operation op : opEnumType.getEnumConstants())
        System.out.printf("%f %s %f = %f%n",
                x, op, y, op.apply(x, y));
}

✔️ 특징

  • 클래스 타입 (Class) 을 전달해야 한다

  • 전달받은 클래스에서 enum 상수 전체를 자동으로 얻을 수 있다

  • 타입 안전하고, enum을 기반으로 반복할 때 유리하다

✅ 장점

  • enum 클래스 하나를 통째로 테스트할 때 편리하다

  • Enum.getEnumConstants()를 통해 열거 상수를 자동으로 얻을 수 있다

❌ 단점

  • 반드시 한 enum 클래스 전체를 테스트해야 한다

  • EarthOperation과 MarsOperation을 같이 넣을 수 없다

📌 방법 2 : 컬렉션 전달 (한정적 와일드카드 사용)

public static void main(String[] args) {
    double x = Double.parseDouble(args[0]);
    double y = Double.parseDouble(args[1]);
    test(Arrays.asList(ExtendedOperation.values()), x, y);
}
private static void test(Collection<? extends Operation> opSet,
                            double x, double y) {
    for (Operation op : opSet)
        System.out.printf("%f %s %f = %f%n",
                x, op, y, op.apply(x, y));
}

✔️ 특징

  • Set이나 List 같은 컬렉션을 직접 전달한다

  • Operation 인터페이스를 구현한 상수라면 무엇이든 섞어서 전달 가능하다

✅ 장점

  • 서로 다른 enum 타입의 상수를 섞어서 테스트할 수 있다 (예: BasicOperation + ExtendedOperation)

  • 사용할 연산만 선택적으로 골라서 넘길 수 있다

❌ 단점

  • enum 클래스처럼 자동으로 상수를 가져올 수는 없다 ➔ 직접 Set.of(...) 같은 방식으로 수동으로 넘겨야 한다

  • 다른 enum 의 상수에 대해선 EnumSet, EnumMap 이 제한된다.

    Set<MarsOperation> s = EnumSet.of(MarsOperation.MARS_PLUS, MarsOperation.MARS_MINUS); // 가능
    
    Set<Operation> s = EnumSet.of(EarthOperation.MINUS, EarthOperation.PLUS); // 불가능
    
    Set<Operation> s = EnumSet.of(EarthOperation.MINUS, MarsOperation.MARS_PLUS); // 불가능

자바 라이브러리의 예시

public enum LinkOption implements OpenOption, CopyOption {
    /**
     * Do not follow symbolic links.
     *
     * @see Files#getFileAttributeView(Path,Class,LinkOption[])
     * @see Files#copy
     * @see SecureDirectoryStream#newByteChannel
     */
    NOFOLLOW_LINKS;
}

❗️ 주의!

  1. 열거 타입끼리는 상속이 불가능하다

  2. 구현 코드가 중복될 수 있다 (각 열거 타입이 비슷한 코드를 가질 수 있음)

  3. 코드 중복이 많아지면 도우미 클래스나 정적 메서드로 분리하는 것이 좋다


🧩 어려웠던 점

  • 처음에는 열거 타입의 제약(상속 불가)이 왜 있는지 이해하기 어려웠다

  • 인터페이스를 통한 확장이라는 개념이 처음에는 복잡해 보였다

💭 느낀 점

  • 같은 연산 인터페이스를 구현하면, 여러 enum을 조합해서 쓸 수 있다는 점이 아주 유용하다고 느꼈다

  • 가끔은 제약이 있는 것이 더 안전한 코드를 작성하는 데 도움이 된다는 것을 느꼈다

Last updated