[Java] Annotation
JavaAnnotation
프로그램의 소스 코드 안에 다른 프로그램을 위한 정보를 미리 약속된 형식으로 포함시킨 것.
주석(comment)처럼 프로그래밍 언어에 영향을 미치지 않으면서도 다른 프로그램에게 유용한 정보를 제공한다.
JDK에서 기본적으로 제공하는 것과 다른 프로그램에서 제공하는 것들이 있다.
java.lang.annotation
Java에서 제공하는 표준 애너테이션.
컴파일러에게 유용한 정보를 제공한다.
새로운 애너테이션을 정의할 때 사용하는 메타 애너테이션(Meta Annotation)을 제공한다.
Annotation | 설명 |
---|---|
@Override | 컴파일러에게 오버라이딩하는 메서드라는 것을 알린다. |
@Deprecated | 앞으로 사용하지 않을 것을 권장하는 대상에 붙인다. |
@SuppressWarnings | 컴파일러의 특정 경고 메시지가 나타나지 않게 해준다. |
@SafeVarargs | 지네릭스 타입의 가변인자에 사용한다. JDK1.7 |
@FunctionalInterface | 함수형 인터페이스라는 것을 알린다. JDK1.8 |
@Target | 애너테이션이 적용 가능한 대상을 지정하는 데 사용한다. |
@Documented | 애너테이션 정보가 javadoc으로 작성된 문서에 포함되게 한다. |
@Inherited | 애너테이션이 자손 클래스에 상속되도록 한다. |
@Retention | 애너테이션이 유지되는 범위를 지정하는데 사용한다. |
@Repeatable | 애너테이션이 반복해서 적용할 수 있게 한다. JDK1.8 |
회색 글씨는 Meta Annotation
@Override
메서드 앞에만 붙일 수 있는 애너테이션.
조상의 메서드를 오버라이딩하는 것이라는 걸 컴파일러에게 알려준다.
같은 이름의 메서드가 조상에 있는 지 확인하고 없으면 에러 메시지를 출력한다.
public class Test {
public static void main(String[] args) {
System.out.println(new Test().toString());
}
@Override
public String toString() {
return "dico는 야채가 아닙니다.";
}
}
- @Override 애너테이션을 붙이지 않을 경우
메서드의 이름을 잘못 적었을 때 컴파일러는 새로운 이름의 메서드가 추가된 것으로 인식한다.
실행 시에도 오류가 발생하지 않고 조상의 메서드가 호출되므로 어디서 잘못되었는지 알아내기 어렵다.
@Deprecated
새로운 JDK 버전이 소개될 때 기존의 기능을 대체할 것들이 추가되기도 한다.
이 때 이미 여러 곳에서 사용되고 있을 지 모르는 기존의 것들을 함부로 삭제할 수가 없어서
이 애너테이션이 붙은 대상은 다른 것으로 대체되었으니 더 이상 사용하지 않을 것을 권하는 의미로 사용된다.
@Deprecated
public Date(int year, int month, int date) {
this(year, month, date, 0, 0, 0);
}
@FunctionalInterface
함수형 인터페이스(Functional Interface)를 선언할 때,
함수형 인터페이스를 올바르게 선언했는 지 확인하고 잘못된 경우 에러를 발생시킨다.
@FunctionalInterface
public interface Runnable {
public abstract void run();
}
함수형 인터페이스는 추상 메서드가 하나뿐이어야 한다.
@SuppressWarnings
컴파일러가 보여주는 경고 메시지가 나타나지 않도록 억제한다.
억제하고자 하는 경고 메시지를 애너테이션 뒤의 괄호() 안에 문자열로 지정한다.
억제할 수 있는 경고 메시지의 종류는 JDK의 버전이 올라가면서 계속 추가될 것이다.
- 메서드 앞에 붙일 경우
메서드 내에서 일어나는 경고들 중 억제한 경고와 관련된 경고들은 나타나지 않게 된다.
나중에 추가된 코드에서 발생할 수도 있는 경고까지 억제될 수 있어 바람직하지 않다.
해당 대상에만 애너테이션을 붙여서 경고의 억제범위를 최소화하는 것이 좋다.
@SuppressWarnings("nuchecked")
ArrayList list = new ArrayList();
list.add(new Object());
- 둘 이상의 경고를 동시에 억제할 경우
배열에서처럼 괄호{}를 사용한다.
@SuppressWarnings({"nuchecked", "varargs"})
@SafeVarargs
메서드에 선언된 가변인자의 타입이 Non-Reifiable 타입일 경우,
해당 메서드를 선언하는 부분과 호출하는 부분에서 'unchecked' 경고가 발생한다.
해당 코드에 문제가 없을 때 'unchecked' 경고를 억제하기 위해 사용한다.
생성자와 static/final이 붙은 메서드에만 사용할 수 있다.
메서드를 호출한 곳에서 발생하는 경고도 억제된다.
'varargs' 경고는 억제할 수 없기 때문에 항상 @SuppressWarnings("varargs")와 같이 사용한다.
@SafeVarargs
@SuppressWarnings("varargs")
public static <T> List<T> asList(T... a) {
return new ArrayList<>();
}
메타 애너테이션 Meta Annotation
애너테이션에 붙이는 애너테이션.
애너테이션을 정의할 때 애너테이션의 적용대상(target)이나 유지기간(retention)등을 지정한다.
- @Target
애너테이션이 적용 가능한 대상을 지정한다.
여러 개의 값을 지정할 때는 배열에서처럼 괄호{}를 사용한다.
ElementType | 의미 |
---|---|
ANNOTATION_TYPE | 애너테이션 |
CONSTRUCTOR | 생성자 |
FIELD | 필드(멤버변수, enum상수) |
LOCAL_VARIABLE | 지역 변수 |
METHOD | 메서드 |
PACKAGE | 패키지 |
PARAMETE | 매개변수 |
TYPE | 타입(클래스, 인터페이스, enum)을 선언할 때 애너테이션을 붙일 수 있다 |
@Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE})
@Retention(RetentionPolicy.SOURCE)
public @interface SuppressWarnings {
@Target(TYPE)
public @interface MyAnnotation{}
@MyAnnotation
class Test{
@MyAnnotation // error
void method(){}
}
- @Retention
애너테이션이 유지되는 기간을 지정한다.
RetentionPolicy | 의미 | 비고 |
---|---|---|
CLASS | 클래스 파일에 존재 실행 시에 사용 불가. 기본값 | 클래스 파일이 JVM에 로딩될 때는 애너테이션의 정보가 무시되어 실행 시에 애너테이션에 대한 정보를 얻을 수 없다. 잘 사용되지 않는다. |
RUNTIME | 클래스 파일에 존재 실행 시에 사용 가능 | 실행 시에 Reflection을 통해 클래스 파일에 저장된 애너테이션의 정보를 읽어서 처리한다. |
SOURCE | 소스 파일에만 존재 클래스 파일에는 존재하지 않음 | 컴파일러에 의해 사용되는 애너테이션의 유지정책. 컴파일러를 직접 작성할 경우에 사용한다. |
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public @interface Override {}
- @Documented
애너테이션에 대한 정보가 javadoc으로 작성한 문서에 포함되도록 한다.
표준 애너테이션 중 '@Override'와 '@SuppressWarnings'를 제외하고는 모두 이 메타 애너테이션이 붙어 있다.
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(value={CONSTRUCTOR, FIELD, LOCAL_VARIABLE, METHOD, PACKAGE, PARAMETER, TYPE})
public @interface Deprecated {}
- @Inherited
애너테이션이 자손 클래스에 상속되도록 한다.
조상 클래스에 붙이면 자손 클래스도 붙은 것으로 인식된다.
@Inherited
@interface SupperAnnotation{}
@SupperAnnotation
class Parent{}
// @Inherited
class Child extends Parent{}
- @Repeatable
같은 이름의 애너테이션 여러 개를 하나의 대상에 적용할 수 있게 한다. (보통 하나의 대상에 한 종류의 애너테이션을 붙인다)
같은 이름의 애너테이션들을 하나로 묶어서 다룰 수 있는 애너테이션도 추가로 정의해야 한다.
@interface MyAnnos { // 여러 개의 MyAnno 애너테이션을 담을 컨테이너 애너테이션.
MyAnno[] value(); // MyAnno 애너테이션 배열 타입의 요소 선언. 반드시 이름이 value여야 한다.
}
@Repeatable(MyAnnos.class) // 괄호 안에 컨테이너 애너테이션을 지정한다.
@interface MyAnno {
String value();
}
@MyAnno("String value()")
@MyAnno("one")
class Test{}
애너테이션 타입 정의하기
'@' 기호를 붙이는 것을 제외하면 인터페이스를 정의하는 것과 동일하다.
@interface AnnotationName{
Type ElementName();
...
}
- 애너테이션의 요소
애너테이션 내에 선언된 메서드.
반환값이 있고 매개변수는 없는 추상 메서드의 형태.
상속을 통해 구현하지 않아도 되지만, 애너테이션을 적용할 때 요소들의 값을 빠짐없이 지정해줘야 한다.
@interface MyAnno {
int method();
String element();
}
@MyAnno(element="4", method=3)
class Test{}
각 요소는 기본값을 가질 수 있다.
괄호{}를 사용해서 여러 개의 기본값을 지정할 수 있다.
@interface MyAnno {
int method() default 2;
String[] element() default {"", "love"};
}
@MyAnno // == @MyAnno(method = 1, element = {"love", "field"})
class Test{}
요소의 타입이 배열인 경우, 괄호{}를 사용해서 여러 개의 값을 지정할 수 있다.
@interface MyAnno {
String[] element();
}
// @MyAnno(element = "love") // 값이 하나일 때
// @MyAnno(element = {"love", "field"}) // 값이 여러 개일 때
@MyAnno(element={}) // 값이 없을 때
class Test {}
요소가 오직 하나뿐이고 이름이 value인 경우,
애너테이션을 적용할 때 요소의 이름을 생략하고 값만 적어도 된다. (배열일 때도 동일)
@interface MyAnno {
int value();
}
@MyAnno(2)
class Test{}
모든 애너테이션의 조상인 Annotation은 애너테이션이 아니라 일반적인 인터페이스로 정의되어 있다.
애너테이션은 상속이 허용되지 않으므로 Annotation을 조상으로 지정할 수 없다.
package java.lang.annotation;
public interface Annotation {
boolean equals(Object obj);
int hashCode();
String toString();
Class<? extends Annotation> annotationType();
}
- Marker Annotation
요소가 하나도 정의되지 않은 애너테이션
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public @interface Override {}
- 애너테이션 요소의 규칙
요소의 타입은 기본형, String, enum, 애너테이션, Class만 허용된다.
() 안에 매개변수를 선언할 수 없다.
예외를 선언할 수 없다.
요소를 타입 매개변수로 정의할 수 없다.
참고 서적: 자바의 정석 3판 2