자바의 애노테이션에 대해 학습하세요.
자바를 개발한 사람은 소스코드에 대한 문서를 만들기 보다, 소스 코드의 문서를 하나의 파일로 만드는 것이 더 낫다고 생각했다. 이로 인해 개발된 것이 JavaDoc -
/** ~ */
이다.
이 기능을 응용하여 프로그램의 소스코드안에 다른 프로그램을 위한 정보를 미리 약속된 형식으로 포함시킨 것이 애노테이션이다.
애노테이션은 사전적으로 주석이라는 의미를 가지고 있으며, 프로그램에 대한 데이터를 제공하는 메타 데이터의 한 형태이다.
모든 애노테이션의 조상은 Annotation이다. 그러나 애노테이션은 상속이 허용되지 않으므로 아래와 같이 명시적으로 Annotation을 조상으로 지정할 수 없다.
@interface TestCode extends Annotation { // 에러. 허용되지 않는 표현
int count();
String testedBy();
}
게다가 Annotation은 애노테이션이 아니라 일반적인 인터페이스로 정의되어 있다.
package java.lang.annotation;
public interface Annotation {
boolean equals(Object obj);
int hashCode();
String toString();
Class<? extends Annotation> annotationType();
}
모든 애노테이션 객체에 대해 equals(), hashCode(), toString() 과 같은 메서드를 호출하는 것이 가능하다.
애노테이션 | 설명 |
---|---|
@Override | 컴파일러에게 오버라이딩하는 메서드라는 것을 알린다. |
@Deprecated | 앞으로 사용하지 않을 것을 권장하는 대상에 붙인다. |
@SuppressWarnings | 컴파일러의 특정 경고메시지가 나타나지 않게 해준다. |
@SafeVarargs | 지네릭스 타입의 가변인자에 사용한다.(JDK1.7) |
@FunctionalInterface | 함수형 인터페이스라는 것을 알린다. |
@Native | native 메서드에서 참조되는 상수 앞에 붙힌다.(JDK1.8) |
애노테이션을 위한 애노테이션이다. 애노테이션을 정의할 때 애노테이션의 적용대상이나 유지기간 등을 지정하는데 사용된다.
애노테이션 | 설명 |
---|---|
@Target | 애노테이션이 적용가능한 대상을 지정하는데 사용한다. |
@Documented | 애노테이션 정보가 javadoc으로 작성된 문서에 포함되게 한다. |
@Inherited | 애노테이션이 자손 클래스에 상속되도록 한다. |
@Retention | 애노테이션이 유지되는 범위를 지정하는데 사용한다. |
@Repeatable | 애노테이션을 반복해서 적용할 수 있게 한다.(JDK1.8) |
값을 지정할 필요가 없는 경우 애노테이션의 요소를 하나도 정의하지 않을 수 있다.
Serializable이나 Cloneable과 같은 요소가 하나도 정의되지 않은 애노테이션을 마커 애노테이션이라고 한다.
public @interface 애노테이션 이름 {
타입 요소이름();
...
}
@interface
를 사용하여 애노테이션 타입을 선언한다.
기호 @
와 interface
는 각자 별개이다. 그래서 둘 사이에 공백을 주어도 문제없이 작동된다. 근데 보기가 안좋다.
public @ interface 애노테이션 이름 {
타입 요소이름();
...
}
애노테이션 내에 선언된 메서드를
애노테이션의 요소
라고 한다.
- 애노테이션의 요소는 반환값이 있고, 매개변수는 없는 추상메서드의 형태를 가진다.
- 상속을 통해 구현하지 않아도 된다. 하지만 적용시 요소들의 값을 빠짐없이 적어야한다(순서는 상관하지 않는다.)
- 예외를 선언할 수 없다.
- default값을 지정할 수 있다.
int count() default 3
- 요소를 타입 매개변수(지네릭)로 정의할 수 없다.
- 요소의 타입은 int, String, enum, 애노테이션, Class만 가질 수 있다.
- 요소는 배열로 여러개를 가질 수 있으며, 만약 요소가 하나라면 요소명은 제외하고 값만 적어도 된다.(배열이여도 요소명이 value면 값만 적을 수 있다.)
아래 예제를 보며 요소의 특징을 활용한다.
public @interface TestCheck {
int count(); // Primitive Type 원시형
String testedBy(); // String
String[] testTools(); // 배열
TestType testType; // enum TestType { FIRST, SECOND }
DateTime testTime(); // 자신이 아닌 애노테이션을(@DateTime) 포함시킬 수 있다.
}
// 애노테이션 적용 예시
@TestCheck(
count = 3, testedBy = "seunghan",
testTools = {"JUnit", "AutoTester"},
testType = TestType.FIRST,
testTime = @DateTime(yyyymmdd = "220202", hhmmss = "001234")
)
public class TestClass {
...
}
메서드 앞에만 붙일 수있는 애너테이션이다. 조상의 메서드를 오버라이딩하는 것이라는걸 컴파일러에게 알려주는 역할을 한다.
// 조상 클래스의 doOverriding 메서드
class Parent {
public void doOverriding() {
...
}
}
// 자손 클래스의 dooverriding 메서드 -> 메서드 명에 오타가 있다.
class Child extends Parent {
public void dooverriding {
...
}
}
위 코드처럼 Child 클래스가 부모인 Parent클래스의 doOverriding을 오버라이딩하려고 했으나 메서드명을 잘못 기입할 때, @Override
가 없다면 컴파일러가 자손의 메서드명이 잘못된 것을 인식하지 못한다(그냥 자손 클래스에 새로운 메서드가 생겨나는 것이다).
@Override를 사용하면 다음과 같이 잘못된 것을 인식한다. 알아내기 어려운 실수를 미연에 방지할 수 있기 떄문에 반드시 붙히는게 좋다.
새로운 버전의 JDK가 소개될 때, 기존의 부족했던 기능들을 개선하면서 그 기능을 새롭게 대체할 것이 추가된다. 하지만 어디서 쓰일지도 모르는 기존의 기능을 삭제할 수는 없다. 따라서 사용되지 않는 필드, 메서드에 @Deprecatede
를 붙혀 더 이상 사용하지 않을 것을 권고한다.
만일 @Deprecated가 붙은 필드 || 메서드를 적용하고 컴파일 하면 다음과 같은 메시지가 나타난다.
Note: AnnotationXXX.java uses or overrides a deprecated API.
Note: Recompile with -Xlint:deprecation for details.
-Xlint:deprecation
: 다시 컴파일하면 상세한 내용을 알 수 있다.
컴파일러가 보여주는 경고메시지를 보이지 않게 억제한다. 경고 메시지에는 여러 종류가 있는데 JDK 버전이 올라갈 수록 계속 추가될 것이다.
억제 가능한 경고 메시지 | 설명 |
---|---|
deprecation | @Deprecated가 사용된 필드 || 메서드를 사용시 발생하는 경고를 억제함 |
unchecked | 지네릭스로 타입을 지정하지 않았을 때 발생하는 경고를 억제함 |
rawtype | 지네릭스를 사용하지 않아서 발생하는 경고 억제함 |
varargs | 가변인자의 타입이 지네릭 타입일 때 발생하는 경고를 억제함 |
- reifiable - 컴파일 이후에 제거되지 않는 타입
- non-reifiable - 컴파일 이후에 제거되는 타입 (지네릭 타입은 대부분 이에 해당함)
메서드에 선언된 가변인자의 타입이 non - reifiable 타입인 경우 해당 메서드를 선언 & 호출하는 부분에서 unchecked 경고가 발생한다. 해당 코드에 문제가 없다면 이 경고를 억제하기 위해 붙힌다.
이 애노테이션은 static, final이 붙은 메서드와 생성자에만 사용이 가능하다. 오버라이딩에는 사용이 불가능하다는 뜻이다.
메서드를 선언할 때 이 애노테이션을 붙히면 호출할 때도 경고 메시지가 억제된다.
추가로 @SafeVarargs로 unchecked 경고를 억제할 수 있지만 varargs 경고는 억제할 수 없다. 따라서 @SuppressWarnings("varargs")를 습관적으로 같이 사용한다.
애너테이션이 유지되는 기간을 지정하는데 사용된다. 애너테이션의 유지 정책의 종류는 다음과 같다.
유지 정책 | 의미 |
---|---|
SORUCE | 소스 파일에만 존재. 클래스파일에는 존재하지 않는다. |
CLASS | 클래스 파일에 존재. 실행시에 사용불가. 기본값 |
RUNTIME | 클래스 파일에 존재. 실행시에 사용가능 |
애너테이션이 적용가능한 대상을 지정하는데 사용된다. 여러 개의 값을 지정할 때는 배열처럼 {}
을 사용한다.
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Target {
/**
* Returns an array of the kinds of elements an annotation type
* can be applied to.
* @return an array of the kinds of elements an annotation type
* can be applied to
*/
ElementType[] value();
}
ElementType을 배열로 적용가능한 대상으로 여러값을 줄 수 있다.
@Target(ElmentType.FIELD, ElementType.METHOD, ElementType.TYPE)
대상 타입 | 의미 |
---|---|
ANNOTATION_TYPE | 애너테이션 |
CONSTRUCTOR | 생성자 |
FIELD | 필드(멤버변수, enum 상수) |
LOCAL_VARIABLE | 지역변수 |
METHOD | 메서드 |
PACKAGE | 패키지 |
PARAMETER | 매개변수 |
TYPE | 타입(클래스, 인터페이스, enum) |
TYPE_PARAMETER | 타입 매개변수(JDK1.8) |
TYPE_USE | 타입이 사용되는 모든 곳 |
애너테이션에 대한 정보가 javadoc으로 작성한 문서에 포함되도록 한다. 자바에서 제공하는 기본 애너테이션 중 @Override
, @SuppressWarnings
을 제외한 모두 이 메타 애너테이션이 붙어있다(@Documented
본인까지도).
package java.lang.annotation;
/**
* Indicates that annotations with a type are to be documented by javadoc
* and similar tools by default. This type should be used to annotate the
* declarations of types whose annotations affect the use of annotated
* elements by their clients. If a type declaration is annotated with
* Documented, its annotations become part of the public API
* of the annotated elements.
*
* @author Joshua Bloch
* @since 1.5
*/
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Documented {
}
애노테이션을 프로세싱하는 기술로 자바 컴파일러 플러그인의 일종으로 애노테이션에 대한 코드베이스를 검사, 수정, 생성하는 역할을 가진다.
애노테이션 프로세서는
컴파일 시점에 끼어들어 특정한 애노테이션이 붙어있는 소스코드를 참조해서 새로운 소스코드를 만들어 낼 수 있는 기능이다. 이 때 새로 생성되는 소스코드는 자바일수도 있고 다른 코드일수도 있다.