Skip to content

Latest commit

 

History

History
107 lines (78 loc) · 5.29 KB

File metadata and controls

107 lines (78 loc) · 5.29 KB

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

명명 패턴의 단점

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

애너테이션

마커 애너테이션

Test라는 이름의 애너테이션을 정의한다고 해보자. 자동으로 수행되는 간단한 애너테이션으로, 예외가 발생하면 해당 테스트를 실패로 처리한다.

// 마커 애너테이션 타입 선언
import java.lang.annotation.*;

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

@Retention(RetentionPolicy.RUNTIME) 메타 애너테이션은 @Test가 런타임에도 유지되어야 한다는 표시다. 만약 이 메타애너테이션을 생략하면 테스트 도구는 @Test를 인식할 수 없다.

@Target(ElementType.METHOD) 메타애너테이션은 @Test가 반드시 메서드 선언에서만 사용돼야 한다고 알려준다. 따라서 클래스 선언, 필드 선언 등 다른 프로그램 요소에는 달 수 없다.

주석에는 “매개변수 없는 정적 메서드 전용이다”라고 쓰여 있다. 이 제약을 컴파일러가 강제할 수 있으면 좋겠지만, 그렇게 하려면 적절한 애너테이션 처리기를 직접 구현해야 한다.

이와 같은 애너테이션을 “아무 매개변수 없이 단순히 대상에 마킹한다”는 뜻에서 마커 애너테이션이라 한다. 이 애너테이션을 사용하면 프로그래머가 Test 이름에 오타를 내거나 메서드 선언 외에 프로그램 요소에 달면 컴파일 오류를 낸다.

매개변수 하나를 받는 애너테이션 타입

특정 예외를 던져야만 성공하는 테스트를 지원하도록 해보자. 그러려면 새로운 애너테이션 타입이 필요하다.

// 매개변수 하나를 받는 애너테이션 타입
import java.lang.annotation.*;

/**
 * 명시한 예외를 던져야만 성공하는 테스트 메서드용 애너테이션
 */
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface ExceptionTest {
	Class<? extends Throwable> value();
}

이 애너테이션의 매개변수 타입은 Class<? extends Throwable>이다. 여기서의 와일드카드 타입은 “Throwable을 확장한 Class 객체”라는 뜻이며, 모든 예외와 오류 타입을 다 수용한다. 이는 한정적 타입 토큰의 또 하나의 활용 사례다.

배열 매개변수를 받는 애너테이션 타입

// 배열 매개변수를 받는 애너테이션 타입
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface ExceptionTest {
	Class<? extends Throwable>[] value();
}

// 배열 매개변수를 받는 애너테이션을 사용하는 코드
@ExceptionTest({ IndexOutOfBoundsException.class, NullPointerException.class })
public static void doublyBad() {
	List<String> list = new ArrayList<>();
	list.addAll(5, null);
}

배열 매개변수를 받는 애너테이션용 문법은 아주 유연하다. 단일 원소 배열에 최적화했지만, 앞서의 @ExceptionTest들도 모두 수정 없이 수용한다. 원소가 여럿인 배열을 지정할 때는 다음과 같이 원소들을 중괄호로 감싸고 쉼표로 구분해주기만 하면 된다.

@Repeatable

자바 8에서는 여러 개의 값을 받는 애너테이션을 다른 방식으로 만들 수 있다. 배열 매개변수를 사용하는 대신 애너테이션에 @Repeatable 메타애너테이션을 다는 방식이다. @Repeatable을 단 애너테이션은 하나의 프로그램 요소에 여러 번 달 수 있다.

단, 주의할 점이 있다.

  • @Repeatable을 단 애너테이션을 반환하는 컨테이너 애너테이션을 하나 더 정의하고, @Repeatable에 이 컨테이너 애너테이션의 class 객체를 매개변수로 전달해야 한다.
  • 컨테이너 애너테이션은 내부 애너테이션 타입의 배열을 반환하는 value 메서드를 정의해야 한다.
  • 컨테이너 애너테이션 타입에는 적절한 보존 정책(@Retention)과 적용 대상(@Target)을 명시해야 한다. 그렇지 않으면 컴파일되지 않는다.
// 반복 가능한 애너테이션 타입
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@Repeatable(ExceptionTestContainer.class)
public @interface ExceptionTest {
	Class<? extends Throwable> value();
}

// 컨테이너 애너테이션
@Retention(RetentionPoilicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface ExceptionTestContainer {
	ExceptionTest[] value();
}

핵심 정리

여러분이 다른 프로그래머가 소스코드에 추가 정보를 제공할 수 있는 도구를 만드는 일을 한다면 적당한 애너테이션 타입도 함께 정의해 제공하자. 애너테이션으로 할 수 있는 일을 명명 패턴으로 처리할 이유는 없다.

도구 제작자를 제외하고는, 일반 프로그래머가 애너테이션 타입을 직접 정의할 일은 거의 없다. 하지만 자바 프로그래머라면 예외 없이 자바가 제공하는 애너테이션 타입들은 사용해야 한다.