📌 Goal
- 애너테이션의 개념을 이해하고 설명할 수 있다.
- 표준 애너테이션과 메타 애너테이션이 어떤 차이가 있고, 어떤 내용이 있는 지 이해할 수 있다.
- 사용자 정의 애너테이션을 작성할 수 있다.
📌 애너테이션 (Annotation)
특정 코드를 사용하는 “프로그램"에게 정보를 전달하는 역할을 한다.
애너테이션은 주석과 유사하게 “정보 전달"에 주요한 목적이 있지만 개발자의 이해를 돕기 위한 주석과는 다르게 다른 프로그램에게 유용한 정보를 제공하기 위해 만들어진 것이다.
✔️ 애너테이션(Annotation)의 주요 역할
- 컴파일러에게 문법 에러를 체크하도록 정보를 제공한다.
- 프로그램을 빌드할 때 코드를 자동으로 생성할 수 있도록 정보를 제공한다.
- 런타임에 특정 기능을 실행하도록 정보를 제공한다.
스프링에서 사용하는 @Test 어노테이션을 예로 들면 아래와 같이 @Test 어노테이션을 사용하여 테스팅을 수행하는 프로그램에게 특정한 메서드만 테스트를 할 수 있도록 정보를 전달할 수 있다.
@Test // 아래 메서드가 테스트 대상임을 테스트 프로그램에게 알리는 애너테이션
public void run() {...생략...}
public void stop() {...생략...}
위의 예제 코드에서 @Test애너테이션을 통해 테스팅을 수행하는 프로그램(예를 들면 가장 많이 사용하는 JUnit)에게 테스트의 대상이 되는 메서드를 전달하고 있다.
정리하면,
위에 정의된 두 개의 메서드 중 @Test애너테이션이 붙여진 run()메서드만 테스트 프로그램에게 테스팅이 필요한 메서드로 전달이 되게 된다. 또한 주석과 마찬가지로 애너테이션은 해당 테스트를 수행하는 프로그램 외의 다른 프로그램들에게는 아무런 영향을 주지 않는다. 즉, 명확한 타겟이 있고, 타겟 외의 대상에게는 없는 것과 다름 없는 것이다.
✔️ 애너테이션(Annotation)의 종류
애너태이션은 크게 두 가지로 구분할 수 있다.
먼저, 자바에서 기본적으로 제공하는 표준 애너테이션과 애너테이션을 정의하는데 사용되는 “애너테이션의 애너테이션"인 메타 애너테이션이 있다.
❗️ 표준 애너테이션
자바에서 기본적으로 제공하는 애너테이션을 말한다.
표준 애너테이션 | 설명 |
@Override | 컴파일러에게 메서드를 오버라이딩하는 것이라고 알림 |
@Deprecated | 앞으로 사용하지 않을 대상을 알릴 때 사용 |
@FunctionalInterface | 함수형 인터페이스라는 것을 알리기 위해 사용 |
@SuppressWarning | 컴파일러가 경고메세지를 나타내지 않음 |
❗️메타 애너테이션
애너테이션에 붙이는 애너테이션으로, 애너테이션을 정의하는데에 사용된다.
메타 애너테이션 | 설명 |
@Target | 애너테이션을 정의할 때 적용 대상을 지정하는데 사용 |
@Documented | 애너테이션 정보를 javadoc으로 작성된 문서에 포함시킴 |
@Inherited | 애너테이션이 하위 클래스에 상속되게 함 |
@Retention | 애너테이션이 유지되는 기간을 정하는데 사용 |
@Repeatable | 애너테이션을 반복해서 적용할 수 있게 함 |
❗️ 사용자 정의 애너테이션
사용자가 직접 정의하는 애너테이션을 말한다.
📌 표준 애너테이션(Standard Annotation)
자바에서 기본적으로 제공하는 애너테이션을 말한다.
✔️ @Override
@Override 애너테이션은 메서드 앞에만 붙일 수 있는 애너테이션으로, 선언한 메서드가 상위 클래스의 메서드를 오버라이딩하는 메서드라는 것을 컴파일러에게 알려주는 역할을 수행한다.
종종 개발자가 코딩을 하다보면 오버라이딩을 할 때 아래의 코드처럼 메서드의 이름을 잘못 작성하는 경우가 있는데, 이러한 경우에 컴파일러는 새로운 이름의 메서드가 추가된 것으로 인식하여 컴파일 단계에서 별다른 에러를 발생시키지 않는다.
class Super {
void run() {}
}
class Sub extends Super {
void rnu() {} // 오버라이딩을 하려고 했으나 오타가 발생함.
}
하지만 실제로 프로그램을 실행하면 런타임에 에러가 발생하거나, 실행 결과가 예측한 것과 다를 수 있으며, 어디에서 무엇이 잘못되었는지 파악하기가 어렵다.
@Override는 이러한 경우를 방지하기 위해 사용된다.
즉, @Override 애너테이션은 컴파일러에게 메서드가 상의 클래스의 메서드를 오버라이딩한 메서드를 알려주어, 개발자의 실수로 인해 오버라이딩이 잘 안됐을 때 해당 메서드에서 에러를 발생시키기 위해 사용된다.
따라서 아래와 같이 메서드를 오버라이딩할 때, @Override를 붙이면 위에서 언급한 상황을 예방할 수 있다.
class Super {
void run() {}
}
class Sub extends Super {
@Override
void rnu() {} // 컴파일 에러 발생, 오타가 난 것을 발견할 수 있음.
}
✔️ @Deprecated
@Deprecated 애너테이션은 새로운 버전의 JDK에서 더 이상 사용하지 않는 필드나 메서드가 있는 경우에 애너테이션을 붙은 대상이 새로운 것으로 대체되었으니 기존의 것을 사용하지 않을 것을 권장하는데 사용된다.
즉, 기존 메서드를 하위 버전 호환성 문제로 삭제하기 곤란해 남겨두어야만 하지만 더 이상 사용하는 것을 권장하지 않을 때 @Deprecated를 사용한다.
아래의 예제 코드를 살펴보자.
class OldClass {
@Deprecated
int oldField;
@Deprecated
int getOldField() { return oldField; };
}
예제 코드를 보면,
oldField와 getOldField() 메서드에 @Deprecated가 표시되어 있다. 이 경우, 앞서 설명했던 것처럼 기존에 사용하던 인스턴스 변수와 메서드가 새로운 것으로 대체되어 더이상 사용하지 않는 것을 권장하고 있다.
참고로, @Deprecated가 붙은 대상을 사용하는 코드를 작성하면, 컴파일할 때 아래와 같은 메시지가 출력된다.
Note: 파일명.java uses or overrides a deprecated API.
Note: Recomplie with -Xlint:deprecation for details.
해당 파일이 @Deprecated된 대상을 사용했으며, -Xlint:deprecation 옵션을 붙여서 실행하면 더 자세한 내용을 확인할 수 있다는 의미이다.
✔️ @SuppressWarnings
@SuppressWarnings 애너테이션은 컴파일 경고 메세지가 출력되지 않도록 할 때 사용된다.
경우에 따라 경고가 발생할 것이 충분히 예상됨에도 묵인해야할 때 주로 사용된다.
아래와 같이 @SuppressWarnings 뒤에 괄호를 붙이고 그 안에 억제하고자 하는 경고메세지를 지정해줄 수 있다.
애너테이션 | 설명 |
@SuppressWarings(”all”) | 모든 경고를 억제 |
@SuppressWarings(”deprecation”) | Deprecated 메서드를 사용한 경우 나오는 경고 억제 |
@SuppressWarings(”fallthrough”) | switch문에서 break 구문이 없을 때 경고 억제 |
@SuppressWarings(”finally”) | finally 관련 경고 억제 |
@SuppressWarings(”null”) | null 관련 경고 억제 |
@SuppressWarings(”unchecked”) | 검증되지 않은 연산자 관련 경고 억제 |
@SuppressWarings(”unused”) | 사용하지 않는 코드 관련 경고 억제 |
더 나아가 아래와 같은 방법으로 둘 이상의 경고를 한번에 묵인할 수 있다.
@SuppressWarnings({"deprecation", "unused", "null"})
✔️ @FunctionalInterface
@FunctionalInteface 애너테이션은 함수영 인터페이스를 선언할 때, 컴파일러가 함수형 인터페이스의 선언이 바르게 선언되었는지 확인하도록 한다. 만약 바르게 선언되지 않은 경우 에러를 발생시킨다.
참고로, 함수형 인터페이스는 단 하나의 추상 메서드만을 가져야하는 제약이 있다.
@FunctionalInterface
public interface Runnable {
public abstract void run (); // 하나의 추상 메서드
}
물론 @FunctionalInteface 애너테이션을 붙이지 않아도 함수형 인터페이스를 선언할 수는 있지만, 코드 작성과정에서 실수를 방지하기 위한 확인용 애너테이션이다.
📌 메타 애너테이션(Meta Annotation)
“애너테이션을 위한 애너테이션"으로 애너테이션의 적용대상 또는 유지 기간을 정하는 등 애너테이션을 정의하는데 사용된다.
✔️ @Target
@Target 애너테이션은 이름 그대로 애너테이션을 적용할 “대상"을 지정하는데 사용된다.
다음의 표에 나와있는 내용이 @Target 애너테이션을 사용하여 지정할 수 있는 대상의 타입이며,
모두 java.lang.annotation.ElementType이라는 열거형에 정의되어 있다.
대상 타입 | 적용범위 |
ANNOTATION_TYPE | 애너테이션 |
CONSTRUCTOR | 생성자 |
FIELD | 필드(멤버변수, 열거형 상수) |
LOCAL_VARIABLE | 지역변수 |
METHOD | 메서드 |
PACKAGE | 패키지 |
PARAMETER | 매개변수 |
TYPE | 타입(클래스, 인터페이스, 열거형) |
TYPE_PARAMETER | 타입 매개변수 |
TYPE_USE | 타입이 사용되는 모든 대상 |
아래 예제 코드를 살펴보자.
import static java.lang.annotation.ElementType.*;
//import문을 이용하여 ElementType.TYPE 대신 TYPE과 같이 간단히 작성할 수 있다.
@Target({FIELD, TYPE, TYPE_USE}) // 적용대상이 FIELD, TYPE
public @interface CustomAnnotation { } // CustomAnnotation을 정의
@CustomAnnotation // 적용대상이 TYPE인 경우
class Main {
@CustomAnnotation // 적용대상이 FIELD인 경우
int i;
}
위의 예제에서 @Target({FIELD, TYPE, TYPE_USE})을 사용하여, 각각 필드, 타입, 그리고 타입이 사용되는 모든 대상(변수)에 애너테이션이 적용되도록 한 것을 확인할 수 있다.
✔️ @Documented
@Documented 애너테이션은 애너테이션에 대한 정보가 javadoc으로 작성한 문서에 포함되도록 하는 애너테이션 설정이다.
자바에서 제공하는 표준 애너테이션과 메타 애너테이션 중 @Override와 @SuppressWarnings를 제외하고는 모두 @Documented가 적용되어 있다.
@Documented
@Target(ElementType.Type)
public @interface CustomAnnotation { }
✔️ @Inherited
@Inherited 애너테이션은 이름에서도 알 수 있듯이 하위 클래스가 애너테이션을 상속받도록 한다.
@Inherited 애너테이션을 상위 클래스에 붙이면, 하위 클래스에도 상위 클래스에 붙은 애너테이션들이 동일하게 적용된다.
@Inherited // @SuperAnnotation이 하위 클래스까지 적용
@interface SuperAnnotation{ }
@SuperAnnotation
class Super { }
class Sub extends Super{ } // Sub에 애너테이션이 붙은 것으로 인식
위의 코드 예제에서, Super상위 클래스로부터 확장된 Sub하위 클래스는 상위 클래스와 동일하게 @SuperAnnotation에 정의된 내용들을 적용받게 된다.
✔️ @Retention
@Retention 애너테이션도 이름 그대로 특정 애너테이션의 지속 시간을 결정하는데 사용된다.
애너테이션과 관련한 유지 정책(Retention Policy)의 종류에는 다음의 세 가지가 존재한다.
※ 유지 정책(Retention Policy)이란?
애너테이션이 유지되는 기간을 지속하는 속성을 말한다.
유지 정책 | 설명 |
SOURCE | 소스 파일에 존재, 클래스파일에는 존재하지 않음 |
CLASS | 클래스 파일에 존재, 실행시에 사용불가, 기본값 |
RUNTIME | 클래스 파일에 존재, 실행시에 사용가능 |
각각의 유지 정책은 언제까지 애너테이션이 유지될 지를 결정한다.
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
//오버라이딩이 제대로 되었는지 컴파일러가 확인하는 용도
//클래스 파일에 남길 필요 없이 컴파일시에만 확인하고 사라짐
public @interface Override(){ }
예를 들면, 위의 예제에서 @Override 애너테이션은 컴파일러가 사용하면 끝나기 때문에, 실행 시에는 더이상 사용되지 않음을 의미한다.
✔️ @Repeatable
@Repeatable 애너테이션은 애너테이션을 여러 번 붙일 수 있도록 허용한다는 의미를 가지고 있다.
예제 코드를 살펴보자.
@Repeatable(Works.class) // ToDo 애너테이션을 여러 번 반복해서 쓸 수 있게 한다.
@interface Work{
String value();
}
사용자 타입의 애너테이션 Todo를 정의하고, @ㄱRepeatable 애너테이션을 사용하여 이것을 여러번 사용할 수 있도록 한다.
@Work("코드 업데이트")
@Work("메서드 오버라이딩")
class Main{
... 생략 ...
}
이제 위와 같이 일반적인 애너테이션과 다르게 ToDo 애너테이션을 하나의 대상에 여러번 적용하는 것이 가능해졌다.
참고로, @Repeatable 애너테이션은 일반적인 애너테이션과 달리 같은 이름의 애너테이션이 여러번 적용될 수 있기 때문에, 이 애너테이션들을 하나로 묶어주는 애너테이션을 별도로 작성해야 한다.
@interface Works { // 여러개의 ToDo애너테이션을 담을 컨테이너 애너테이션 ToDos
Work[] value();
}
@Repeatable(Works.class) // 컨테이너 애너테이션 지정
@interface Work {
String value();
}
📌 사용자 정의 애너테이션(Custom Annotation)
사용자가 직접 애너테이션을 정의해서 사용하는 것을 의미한다.
애너테이션을 정의하는 방법은 인터페이스를 정의하는 것과 비슷하다.
@interface 애너테이션명 { // 인터페이스 앞에 @기호만 붙이면 애너테이션을 정의할 수 있습니다.
타입 요소명(); // 애너테이션 요소를 선언
}
한 가지 유의할 점은, 사용자 정의 애너테이션은 java.lang.annotation 인터페이스를 상속받기 때문에 다른 클래스나 인터페이스를 상속 받을 수 없다.
댓글