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

// 확장 가능한 열거 타입을 위한 인터페이스
interface Animal {
    void sound();
}

// 기본 열거 타입 구현
enum DefaultAnimal implements Animal {
    DOG {
        @Override
        public void sound() {
            System.out.println("Woof");
        }
    },
    CAT {
        @Override
        public void sound() {
            System.out.println("Meow");
        }
    },
    BIRD {
        @Override
        public void sound() {
            System.out.println("Tweet");
        }
    }
}

// 새로운 열거 타입을 추가하기 위해 Animal을 구현하는 열거 타입 정의
enum CustomAnimal implements Animal {
    LION {
        @Override
        public void sound() {
            System.out.println("Roar");
        }
    },
    ELEPHANT {
        @Override
        public void sound() {
            System.out.println("Trumpet");
        }
    }
}

public class Main {
    public static void main(String[] args) {
        // 기본 열거 타입 사용
        DefaultAnimal.DOG.sound();  // 출력: Woof
        DefaultAnimal.CAT.sound();  // 출력: Meow

        // 새로운 열거 타입 사용
        CustomAnimal.LION.sound();  // 출력: Roar
        CustomAnimal.ELEPHANT.sound();  // 출력: Trumpet
    }
}

이 예시 코드에서는 Animal 인터페이스를 정의하여 열거 타입이 구현하도록 하였다. 그리고 DefaultAnimal이라는 기본 열거 타입과 CustomAnimal이라는 새로운 열거 타입을 정의하고 각각의 상수에 대해 다른 동작을 구현하였다. 클라이언트는 Animal 인터페이스를 사용하여 두 열거 타입의 인스턴스를 다룰 수 있다.


39] 명명 패턴보다 애너테이션을 사용하라

전통적으로 도구나 프레임워크가 특별히 다뤄야 할 프로그램 요소에는 딱 구분되는 명명 패턴을 적용해왔다. 예컨대 테스트 프레임워크인 Junit은 버전 3까지 테스트 메서드 이름을 test로 시작하게끔 했다. 효과적인 방법이지만 단점도 크다.

  1. 첫 번째, 오타가 나면 안 된다.
  2. 두 번째, 올바른 프로그램 요소에서만 사용되리라 보증할 방법이 없다는 것이다.
  3. 세 번째, 프로그램 요소를 매개변수로 전달할 마땅한 방법이 없다는 것이다.

애너테이션은 이 모든 문제를 해결해주는 멋진 개념으로, JUnit도 버전 4부터 전면 도입하였다. 이번 아이템에서는 애너테이션의 동작 방식을 보여주고자 직접 제작한 작은 테스트 프레임워크를 사용할 것이다. Test라는 이름의 애너테이션을 정의한다고 해보자. 자동으로 수행되는 간단한 테스트용 애너테이션으로, 예외가 발생하면 해당 테스트를 실패로 처리한다.

import java.lang.annotation.*;

/**
 * 테스트 메서드임을 선언하는 애너테이션이다.
 * 매개변수 없는 정적 메서드 전용이다.
 */
 
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Test {
}

보다시피 @Test 애너테이션 타입 선언 자체에도 두 가지의 다른 애너테이션이 달려있다. 바로 @Retention과 @Target이다.  이처럼 애너테이션 선언에 다는 애너테이션을 메나애너테이션(meta-annotation)이라 한다.

@Retention(RetentionPolicy.RUNTIME) 메타애너테이션은 @Test가 런타임에도 유지되어야 한다는 표시다. 만약 이 메타애너테이션을 생략하면 테스트 도구는 @Test를 인식할 수 없다. 한편, @Target(ElementType.METHOD) 메타애너테이션은 @Test 가 반드시 메서드 선언에서만 사용돼야 한다고 알려준다. 따라서 클래스 선언, 필드 선언 등 다른 프로그램 요소에는 달 수 없다.

앞 코드의 메서드 주석에는 "매개변수 없는 정적 메서드 전용이다"라고 쓰여 있다. 이 제약을 컴파일러가 강제할 수 있으면 좋겠지만, 그렇게 하려면 적절한 애너테이션 처리기를 직접 구현해야 한다. 적절한 애너테이션 처리기 없이 인스턴스 메서드나 매개변수가 있는 메서드에 달면 어떻게 될까? 컴파일을 잘 되겠지만, 테스트 도구를 실행할 때 문제가 된다.

다음 코드는 @Test 애너테이션을 실제 적용한 모습이다. 이와 같은 애너테이션을 "아무 매개변수 없이 단순히 대상에 마킹(marking)한다" 는 뜻에서 마커 애너테이션이라 한다. 이 애너테이션을 사용하면 프로그래머가 Test 이름에 오타를 내거나 메서드 선언 외의 프로그램 요소에 달면 컴파일 오류를 내준다.