어노테이션
어노테이션에는 여러 가지 용도가 있습니다:
- 컴파일러를 위한 정보 - 컴파일러는 어노테이션을 사용하여 오류를 감지하거나 경고를 표시하지 않을 수 있습니다.
- 컴파일 시간 및 배포 시간 처리 - 소프트웨어 도구는 어노테이션 정보를 처리하여 코드, XML 파일 등을 생성할 수 있습니다.
- 런타임 처리 - 일부 어노테이션은 런타임에 검사할 수 있습니다.
이 섹션에서는 어노테이션을 사용할 수 있는 위치, 어노테이션을 적용하는 방법, Java 플랫폼, 표준 에디션(Java SE API)에서 사용할 수 있는 사전 정의된 어노테이션 유형, 플러그 가능한 유형 시스템과 함께 유형 어노테이션을 사용하여 더 강력한 유형 검사 기능을 갖춘 코드를 작성하는 방법 및 반복 어노테이션을 구현하는 방법에 대해 설명합니다.
어노테이션의 형식
가장 간단한 형식의 어노테이션은 다음과 같습니다:
@Entityat 기호(@)는 컴파일러에게 다음에 오는 내용이 어노테이션임을 나타냅니다. 다음 예제에서 어노테이션의 이름은 Override입니다:
@Override
void mySuperMethod() { ... }어노테이션에는 이름을 지정하거나 지정하지 않을 수 있는 elements 가 포함될 수 있으며, 해당 요소에 대한 값이 있습니다:
@Author(
name = "Benjamin Franklin",
date = "3/27/2003"
)
class MyClass { ... }또는
@SuppressWarnings(value = "unchecked")
void myMethod() { ... }value라는 요소가 하나만 있는 경우 다음과 같이 이름을 생략할 수 있습니다:
@SuppressWarnings("unchecked")
void myMethod() { ... }어노테이션에 요소가 없는 경우 앞의 @Override 예시와 같이 괄호를 생략할 수 있습니다.
동일한 선언에 여러 개의 어노테이션을 사용할 수도 있습니다:
@Author(name = "Jane Doe")
@EBook
class MyClass { ... }어노테이션의 유형이 같은 경우 이를 반복 어노테이션이라고 합니다:
@Author(name = "Jane Doe")
@Author(name = "John Smith")
class MyClass { ... }반복 어노테이션은 Java SE 8 릴리스부터 지원됩니다. 자세한 내용은 반복 어노테이션 섹션을 참조하세요.
어노테이션 유형은 Java SE API의 java.lang 또는 java.lang.annotation 패키지에 정의된 유형 중 하나 일 수 있습니다. 앞의 예제에서 Override 및 SuppressWarnings는 미리 정의된 Java 어노테이션입니다. 자신만의 어노테이션 유형을 정의할 수도 있습니다. 앞의 예제에서 Author 및 Ebook 어노테이션은 사용자 정의 어노테이션 유형입니다.
어노테이션을 사용할 수 있는 위치
어노테이션은 클래스, 필드, 메서드 및 기타 프로그램 요소의 선언과 같은 선언에 적용할 수 있습니다. 선언에 사용되는 경우 각 어노테이션은 관례에 따라 자체 줄에 표시되는 경우가 많습니다.
Java SE 8 릴리스부터 어노테이션은 타입 사용에도 적용될 수 있습니다. 다음은 몇 가지 예시입니다:
-
클래스 인스턴스 생성 표현식:
new @Interned MyObject(); -
캐스트 입력:
myString = (@NonNull String) str; -
implements clause:
class UnmodifiableList<T> implements @Readonly List<@Readonly T> { ... } -
Thrown exception declaration:
void monitorTemperature() throws @Critical TemperatureException { ... }
이러한 형태의 어노테이션을 type annotation 이라고 합니다.
어노테이션 타입 선언하기
많은 어노테이션이 코드에서 주석을 대체합니다.
소프트웨어 그룹이 전통적으로 모든 클래스의 본문을 중요한 정보를 제공하는 주석으로 시작한다고 가정해 보겠습니다:
public class Generation3List extends Generation2List {
// Author: John Doe
// Date: 3/17/2002
// Current revision: 6
// Last modified: 4/12/2004
// By: Jane Doe
// Reviewers: Alice, Bill, Cindy
// class code goes here
}동일한 메타데이터에 어노테이션을 추가하려면 먼저 어노테이션 유형을 정의해야 합니다. 이를 위한 구문은 다음과 같습니다:
@interface ClassPreamble {
String author();
String date();
int currentRevision() default 1;
String lastModified() default "N/A";
String lastModifiedBy() default "N/A";
// Note use of array
String[] reviewers();
}어노테이션 타입 정의는 인터페이스 정의와 비슷하게 보이는데, 인터페이스 키워드 앞에 @ 기호가 붙습니다(@ = AT, 어노테이션 타입에서와 같이). 어노테이션 타입은 인터페이스의 한 형태로, 이후 섹션에서 다룰 예정입니다. 지금은 인터페이스를 이해할 필요가 없습니다.
이전 어노테이션 정의의 본문에는 메서드와 매우 유사하게 보이는 어노테이션 타입 요소 선언이 포함되어 있습니다. 선택적 기본값을 정의할 수 있다는 점에 유의하세요.
어노테이션 타입이 정의된 후에는 다음과 같이 값을 입력한 해당 타입의 어노테이션을 사용할 수 있습니다:
@ClassPreamble (
author = "John Doe",
date = "3/17/2002",
currentRevision = 6,
lastModified = "4/12/2004",
lastModifiedBy = "Jane Doe",
// Note array notation
reviewers = {"Alice", "Bob", "Cindy"}
)
public class Generation3List extends Generation2List {
// class code goes here
}Note:
@ClassPreamble의 정보를 자바독에서 생성된 문서에 표시하려면@ClassPreamble정의에@Documented어노테이션을 추가해야 합니다:
// import this to use @Documented
import java.lang.annotation.*;
@Documented
@interface ClassPreamble {
// Annotation element definitions
}
사전 정의된 어노테이션 타입
어노테이션 타입 세트는 Java SE API에 미리 정의되어 있습니다. 일부 어노테이션 타입은 Java 컴파일러에서 사용되며, 일부는 다른 어노테이션에 적용됩니다.
자바 언어에서 사용하는 어노테이션 타입
java.lang에 정의된 미리 정의된 어노테이션 타입은 @Deprecated, @Override, @SuppressWarnings 입니다.
@Deprecated
@Deprecated 어노테이션은 표시된 요소가 사용되지 않으므로 더 이상 사용해서는 안 됨을 나타냅니다. 컴파일러는 프로그램이 메서드, 클래스 또는 필드에 @Deprecated 어노테이션이 있는 것을 사용할 때마다 경고를 생성합니다. 요소가 더 이상 사용되지 않는 경우 다음 예시와 같이 Javadoc @deprecated 태그를 사용하여 문서화해야 합니다. Javadoc 주석과 어노테이션 모두에서 @ 기호를 사용하는 것은 우연이 아니라 개념적으로 관련이 있습니다. 또한 Javadoc 태그는 소문자 d로 시작하고 어노테이션은 대문자 D로 시작한다는 점에 유의하세요.
// Javadoc comment follows
/**
* @deprecated
* explanation of why it was deprecated
*/
@Deprecated
static void deprecatedMethod() { }Java SE 9부터 forRemoval 속성이 @Deprecated 어노테이션에 추가되었습니다. 이 속성은 어노테이션된 요소가 향후 버전에서 제거될 수 있는지 여부를 나타냅니다. 기본값은 false입니다.
@Override
@Override 어노테이션은 컴파일러에게 해당 엘리먼트가 수퍼클래스에 선언된 엘리먼트를 재정의하도록 되어 있음을 알려줍니다. 메서드 재정의는 인터페이스와 상속 섹션에서 설명합니다.
// mark method as a superclass method
// that has been overridden
@Override
int overriddenMethod() { }메서드를 재정의할 때 이 어노테이션을 반드시 사용해야 하는 것은 아니지만 오류를 방지하는 데 도움이 됩니다. @Override로 표시된 메서드가 슈퍼클래스 중 하나의 메서드를 올바르게 재정의하지 못하면 컴파일러가 오류를 생성합니다.
@SuppressWarnings
@SuppressWarnings 어노테이션은 컴파일러가 생성할 특정 경고를 억제하도록 지시합니다. 다음 예제에서는 더 이상 사용되지 않는 메서드가 사용되며 컴파일러는 일반적으로 경고를 생성합니다. 그러나 이 경우 어노테이션을 사용하면 경고가 억제됩니다.
// use a deprecated method and tell
// compiler not to generate a warning
@SuppressWarnings("deprecation")
void useDeprecatedMethod() {
// deprecation warning
// - suppressed
objectOne.deprecatedMethod();
}모든 컴파일러 경고는 카테고리에 속합니다. Java 언어 사양에는 deprecation과 unchecked이라는 두 가지 범주가 있습니다. unchecked경고는 제네릭이 등장하기 전에 작성된 레거시 코드와 인터페이스할 때 발생할 수 있습니다. 여러 범주의 경고를 억제하려면 다음 구문을 사용하세요:
@SuppressWarnings({"unchecked", "deprecation"})@SafeVarargs
@SafeVarargs 어노테이션을 메서드나 생성자에 적용하면 코드가 해당 변수에 대해 잠재적으로 안전하지 않은 연산을 수행하지 않음을 주장합니다. 이 어노테이션 타입을 사용하면 varargs 사용과 관련된 체크되지 않은 경고가 표시되지 않습니다.
@FunctionalInterface
Java SE 8에 도입된 @FunctionalInterface 어노테이션은 타입 선언이 Java 언어 사양에 정의된 대로 함수형 인터페이스를 위한 것임을 나타냅니다.
다른 어노테이션에 적용되는 어노테이션
다른 어노테이션에 적용되는 어노테이션을 메타 어노테이션이라고 합니다. 메타 어노테이션 타입은 java.lang.annotation에 여러 가지가 정의되어 있습니다.
@Retention
@Retention 어노테이션은 표시된 어노테이션이 저장되는 방식을 지정합니다:
RetentionPolicy.SOURCE- 표시된 어노테이션은 소스 수준에서만 유지되며 컴파일러에서는 무시됩니다.RetentionPolicy.CLASS- 컴파일 시 컴파일러에 의해 표시된 어노테이션이 유지되지만 자바 가상 머신(JVM)에 의해 무시됩니다.RetentionPolicy.RUNTIME- 표시된 어노테이션은 런타임 환경에서 사용할 수 있도록 JVM에 의해 유지됩니다.
@Documented
@Documented 어노테이션은 지정된 어노테이션이 사용될 때마다 해당 요소를 Javadoc 도구를 사용하여 문서화해야 함을 나타냅니다. (기본적으로 어노테이션은 Javadoc에 포함되지 않습니다.) 자세한 내용은 Javadoc 도구 페이지를 참조하세요.
@Target
@Target 어노테이션은 어노테이션이 적용될 수 있는 Java 요소의 종류를 제한하는 다른 어노테이션을 표시합니다. 대상 어노테이션은 다음 요소 타입 중 하나를 값으로 지정합니다:
ElementType.ANNOTATION_TYPE는 어노테이션 타입에 적용할 수 있습니다.ElementType.CONSTRUCTOR는 생성자에 적용할 수 있습니다.ElementType.FIELD는 필드 또는 속성에 적용할 수 있습니다.ElementType.LOCAL_VARIABLE는 로컬 변수에 적용할 수 있습니다.ElementType.METHOD는 메서드에 적용할 수 있습니다.ElementType.MODULE는 모듈 선언에 적용할 수 있습니다..ElementType.PACKAGE는 패키지 선언에 적용할 수 있습니다.ElementType.PARAMETER는 메서드의 매개 변수에 적용할 수 있습니다.ElementType.RECORD_COMPONENT는 레코드의 구성 요소에 적용할 수 있습니다.ElementType.TYPE는 클래스, 추상 클래스, 인터페이스, 어노테이션 인터페이스, 열거형 또는 레코드 선언에 적용할 수 있습니다.ElementType.TYPE_PARAMETER는 타입 매개변수에 적용할 수 있습니다.ElementType.TYPE_USE는 필드 선언과 같이 타입이 사용되는 곳에 적용할 수 있습니다.
@Inherited
@Inherited 어노테이션은 어노테이션 타입이 수퍼클래스로부터 상속될 수 있음을 나타냅니다. (기본적으로 참이 아닙니다.) 사용자가 어노테이션 타입을 쿼리할 때 클래스에 이 타입에 대한 어노테이션이 없으면 클래스의 수퍼클래스에서 어노테이션 타입을 쿼리합니다. 이 어노테이션은 클래스 선언에만 적용됩니다.
@Repeatable
Java SE 8에 도입된 @Repeatable 어노테이션은 표시된 어노테이션이 동일한 선언 또는 타입 사용에 두 번 이상 적용될 수 있음을 나타냅니다. 자세한 내용은 반복 어노테이션 섹션을 참조하세요.
타입 어노테이션 및 플러그형 타입 시스템
Java SE 8 릴리스 이전에는 어노테이션을 선언에만 적용할 수 있었습니다. Java SE 8 릴리스부터는 모든 타입 사용에도 어노테이션을 적용할 수 있습니다. 즉, 타입을 사용하는 모든 곳에 어노테이션을 사용할 수 있습니다. 타입이 사용되는 몇 가지 예로는 클래스 인스턴스 생성 표현식(new), 형 변환, 구현 절, Throw 절 등이 있습니다. 이러한 형태의 어노테이션을 타입 어노테이션이라고 하며, 어노테이션 기초 섹션에 몇 가지 예가 나와 있습니다.
타입 어노테이션은 보다 강력한 타입 검사를 보장하는 향상된 Java 프로그램 분석 방법을 지원하기 위해 만들어졌습니다. Java SE 8 릴리스에서는 타입 검사 프레임워크가 제공되지 않지만, Java 컴파일러와 함께 사용되는 하나 이상의 플러그 가능한 모듈로 구현되는 타입 검사 프레임워크를 작성(또는 다운로드)할 수 있습니다.
예를 들어, 프로그램에서 특정 변수가 절대 null로 할당되지 않도록 하여 NullPointerException이 트리거되지 않도록 하고 싶을 수 있습니다. 이를 확인하기 위해 사용자 정의 플러그인을 작성할 수 있습니다. 그런 다음 코드를 수정하여 특정 변수가 null에 할당되지 않았음을 나타내는 어노테이션을 추가하면 됩니다. 변수 선언은 다음과 같을 수 있습니다:
@NonNull String str;명령줄에 NonNull 모듈을 포함하여 코드를 컴파일할 때 컴파일러가 잠재적인 문제를 감지하면 경고를 출력하여 오류를 피하기 위해 코드를 수정할 수 있도록 합니다. 코드를 수정하여 모든 경고를 제거한 후에는 프로그램이 실행될 때 이 특정 오류가 발생하지 않습니다.
각 모듈이 다른 종류의 오류를 검사하는 여러 타입 검사 모듈을 사용할 수 있습니다. 이러한 방식으로 Java 타입 시스템을 기반으로 구축하여 원하는 시기와 위치에 특정 검사를 추가할 수 있습니다.
타입 어노테이션을 신중하게 사용하고 플러그 가능한 타입 검사기를 사용하면 더 강력하고 오류 발생 가능성이 적은 코드를 작성할 수 있습니다.
대부분의 경우 직접 타입 검사 모듈을 작성할 필요가 없습니다. 이 작업을 대신 해 주는 써드파티가 있습니다. 예를 들어, 워싱턴 대학교에서 만든 검사 프레임워크를 활용할 수 있습니다. 이 프레임워크에는 NonNull 모듈과 정규식 모듈, 뮤텍스 잠금 모듈이 포함되어 있습니다. 자세한 내용은 검사기 프레임워크를 참조하세요.
반복 어노테이션
선언이나 타입 사용에 동일한 어노테이션을 적용하고 싶은 경우가 있습니다. Java SE 8 릴리스부터 반복 어노테이션을 사용하면 이 작업을 수행할 수 있습니다.
예를 들어, 주어진 시간 또는 특정 일정에 따라 메서드를 실행할 수 있는 타이머 서비스를 사용하는 코드를 작성하고 있다고 가정해 보겠습니다(예: UNIX cron 서비스와 유사). 이제 매월 마지막 날과 매주 금요일 오후 11:00에 doPeriodicCleanup()이라는 메서드를 실행하도록 타이머를 설정하려고 합니다. 타이머를 실행하도록 설정하려면 @Schedule 어노테이션을 생성하고 이를 doPeriodicCleanup() 메서드에 두 번 적용합니다. 첫 번째 사용은 다음 코드 예시와 같이 매월 마지막 날을 지정하고 두 번째 사용은 금요일 오후 11시를 지정합니다:
@Schedule(dayOfMonth="last")
@Schedule(dayOfWeek="Fri", hour="23")
public void doPeriodicCleanup() { ... }이전 예제에서는 메서드에 어노테이션을 적용했습니다. 표준 어노테이션을 사용하는 모든 곳에서 어노테이션을 반복할 수 있습니다. 예를 들어 무단 액세스 예외를 처리하는 클래스가 있다고 가정해 보겠습니다. 이 클래스에 관리자용 @Alert 어노테이션과 관리자용 어노테이션을 하나씩 추가합니다:
@Alert(role="Manager")
@Alert(role="Administrator")
public class UnauthorizedAccessException extends SecurityException { ... }호환성을 위해 반복 어노테이션은 Java 컴파일러에서 자동으로 생성되는 컨테이너 어노테이션에 저장됩니다. 컴파일러가 이 작업을 수행하려면 코드에 두 개의 선언이 필요합니다.
1단계: 반복 가능한 어노테이션 타입 선언하기
어노테이션 타입은 @Repeatable 메타 어노테이션으로 표시해야 합니다. 다음 예는 사용자 정의 @Schedule 반복 가능 어노테이션 타입을 정의합니다:
@Repeatable(Schedules.class)
public @interface Schedule {
String dayOfMonth() default "first";
String dayOfWeek() default "Mon";
int hour() default 12;
}괄호 안의 @Repeatable 메타 어노테이션의 값은 Java 컴파일러가 반복 어노테이션을 저장하기 위해 생성하는 컨테이너 어노테이션의 타입입니다. 이 예제에서 포함하는 어노테이션 타입은 @Schedules이므로 반복되는 @Schedule 어노테이션은 @Schedules 어노테이션에 저장됩니다.
선언을 반복 가능한 것으로 먼저 선언하지 않고 동일한 어노테이션을 선언에 적용하면 컴파일 타임 오류가 발생합니다.
Step 2: Containing 어노테이션 타입 선언하기
containing 어노테이션 타입은 배열 타입의 value 요소를 가져야 합니다. 배열 타입의 컴포넌트 타입은 반복 가능한 어노테이션 타입이어야 합니다. containing 어노테이션 타입인 @Schedules에 대한 선언은 다음과 같습니다:
public @interface Schedules {
Schedule[] value();
}어노테이션 가져오기
Reflection API에는 어노테이션을 가져는 데 사용할 수 있는 몇 가지 메서드가 있습니다. AnnotatedElement.getAnnotation(Class<T>)와 같이 단일 어노테이션을 반환하는 메서드의 동작은 요청된 타입의 어노테이션이 하나만 있는 경우에만 단일 어노테이션을 반환한다는 점에서 변함이 없습니다. 요청된 타입의 어노테이션이 두 개 이상 있는 경우, 먼저 컨테이너 어노테이션을 가져와서 어노테이션을 가져올 수 있습니다. 이러한 방식으로 레거시 코드는 계속 작동합니다. Java SE 8에서는 컨테이너 어노테이션을 스캔하여 한 번에 여러 어노테이션을 반환하는 다른 메서드도 도입되었습니다(예: AnnotatedElement.getAnnotationsByType(Class<T>). 사용 가능한 모든 메서드에 대한 자세한 내용은 AnnotatedElement 클래스 사양을 참조하세요.
디자인 고려 사항
어노테이션 타입을 디자인할 때는 해당 타입의 어노테이션의 카디널리티를 고려해야 합니다. 이제 어노테이션을 0회, 1회, 또는 어노테이션의 타입이 @Repeatable으로 표시된 경우 두 번 이상 사용할 수 있습니다. 또한 @Target 메타 어노테이션을 사용하여 어노테이션 타입을 사용할 수 있는 위치를 제한할 수 있습니다. 예를 들어 메서드와 필드에만 사용할 수 있는 반복 가능한 어노테이션 타입을 만들 수 있습니다. 어노테이션을 사용하는 프로그래머가 최대한 유연하고 강력하게 사용할 수 있도록 어노테이션 타입을 신중하게 설계하는 것이 중요합니다.