애노테이션이란?
프로그램의 소스코드 안에 다른 프로그램을 위한 정보를 미리 약속된 형식으로 포함시킨 것이 바로 애노테이션(annotation)이다.애노테이션은 주석처럼 프로그래밍 언어에 영향을 미치지 않으면서도 다른 프로그램에게 유용한 정보를 제공할 수 있다는 장점이 있다.
이클립스나 인텔리제이 등의 IDE를 사용했다면 프로그램이 자동적으로 '@Override'라는 애노테이션을 붙여주는 것을 본 적이 있을 것이다. 만약 부모 클래스의 메소드를 오버라이딩하고자 하는데, 다음과 같이 프로그래머의 실수로 메소드명을 잘못 작성한다면 컴파일러는 그저 새로운 이름의 메소드가 추가된 것으로 인식한다.
그러나 아래와 같이 메소드 앞에 '@Override'라고 애노테이션을 붙이면 컴파일러가 같은 이름의 메소드가 조상에 있는지 확인하고 없다면 에러메시지를 출력한다. 이를 통해 알아내기 어려운 실수를 컴파일하는 과정에서 미리 잡아낼 수 있다.
에러메세지는 다음과 같다.
java: method does not override or implement a method from a supertype
표준 애노테이션
자바에서는 기본적으로 몇가지 애노테이션을 제공한다. 아래의 표에서 *가 붙은 것들은 메타 에노테이션(meta annotation)으로 애노테이션을 정의하는데 사용되는 애노테이션의 애노테이션이다.
애노테이션 | 설명 |
@Override | 컴파일러에게 오버라이딩하는 메소드라는 것을 알린다. |
@Deprecated | 앞으로 사용하지 않을 것을 권장하는 대상에 붙인다. |
@SuppressWarnings | 컴파일러의 특정 경고메시지가 나타나지 않게 해준다. |
@Native | 제네릭스 타입의 가변인자에 사용한다. |
@Target* | 함수형 인터페이스라는 것을 알린다. |
@Documentated* | native메소드에서 참조되는 상수 앞에 붙인다. |
@Inherited* | 애노테이션이 자손 클래스에 상속되도록 한다. |
@Retention* | 애노테이션이 유지되는 범위를 지정하는 데 사용한다. |
@Repeatable* | 애노테이션을 반복해서 적용할 수 있게 한다. |
메타 에노테이션
메타 에노테이션은 '에노테이션을 위한 에노테이션', 즉 애노테이션에 붙이는 애노테이션으로 애노테이션을 정의할 때 에노테이션의 적용대상(target)이나 유지기간(retention) 등을 지정하는 데 사용된다.
@Target
애노테이션이 적용가능한 대상을 지정하는 데 사용된다. 아래는 '@SuppressWarnings'를 정의한 것인데, 이 애노테이션에 적용할 수 있는 대상을 '@Target'으로 지정했다. 여러 개의 값을 지정할 때는 배열에서처럼 괄호를 사용해야한다.
@Target({ TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE })
@Retention(RetentionPolicy.SOURCE)
public @interface SuppressWarnings {
String[] value();
}
@Target으로 지정할 수 있는 애노테이션 적용대상의 종류는 아래와 같다.
대상타입 | 의미 |
ANNOTATION_TYPE | 애노테이션 |
CONSTRUCTOR | 생성자 |
FIELD | 필드(멤버변수, enum상수) |
LOCAL_VARIABLE | 지역변수 |
METHOD | 메소드 |
PACKAGE | 패키지 |
PARAMETER | 매개변수 |
TYPE | 타입(클래스, 인터페이스, enum) |
TYPE_PARAMETER | 타입 매개변수 |
TYPE_USE | 타입이 사용되는 모든 곳 |
'TYPE'은 타입을 선언할 떄, 애노테이션을 붙일 수 있다는 뜻이고 'TYPE_USE'는 해당 타입의 변수를 선언할 때 붙일 수 있다는 뜻이다. 'FIELD'는 기본형에, 'TYPE_USE'는 참조형에 사용된다는 점에 주의해야 한다.
@Target({ FIELD, TYPE, TYPE_USE }) // 적용대상이 FIELD, TYPE, TYPE_USE
public @interface MyAnnotation { } // MyAnnotation을 정의
@MyAnnotation // 적용대상이 TYPE인 경우
class Myclass {
@MyAnnotation // 적용대상이 FIELD인 경우
int i;
@MyAnnotation // 적용대상이 TYPE_USE인 경우
Myclass mc;
}
@Retention
애노테이션이 유지되는 기간을 지정하는 데 사용된다. 애노테이션의 유지 정책(retention policy)의 종류는 다음과 같다.
유지 정책 | 의미 |
SOURCE | 소스 파일에만 존재. 클래스파일에는 존재하지 않음. |
CLASS | 클래스 파일에 존재. 실행 시에 사용불가. 기본값 |
RUNTIME | 클래스 파일에 존재. 실행 시에 사용가능 |
'@Override'나 '@SuppressWarnings'처럼 컴파일러가 사용하는 애너테이션은 유지 정책이 'SOURCE'이다. 컴파일러를 직접 작성할 것이 아니면, 이 유지정책은 필요없다.
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public @interface Override {}
유지 정책을 'RUNTIME'으로 하면 실행 시에 리플렉션(reflection)을 통해 클래스 파일에 저장된 애노테이션의 정보를 읽어서 처리할 수 있다. '@FunctionalInterface'는 '@Override'처럼 컴파일러가 체크해주는 애노테이션이지만, 실행 시에도 사용되므로 유지 정책이 'RUNTIME'으로 되어 있다.
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface FunctionalInterface {}
유지 정책 'CLASS'는 컴파일러가 애노테이션의 정보를 클래스 파일에 저장할 수 있게는 하지만, 클래스 파일이 JVM에 로딩될 때는 애노테이션의 정보가 무시되어 실행 시에 애노테이션에 대한 정보를 얻을 수 없다. 이것이 CLASS가 유지정책의 기본값임에도 불구하고 잘 사용되지 않는 이유이다.
@Documented
애노테이션에 대한 정보가 javadoc으로 작성한 문서에 포함되도록 한다. 자바에서 제공하는 기본 애노테이션 중에 '@Override'와 '@SuppressWarnings'를 제외하고는 모두 이 메타 애노테이션이 붙어 있다.
애노테이션 정의하는 방법
직접 애노테이션을 만들어서 사용하는 것도 가능하다. 새로운 에너테이션을 정의하는 방법은 '@'기호를 붙이는 것을 제외하면 인터페이스를 정의하는 것과 동일하다.
@interface 애노테이션이름 {
타입 요소이름(); // 애노테이션의 요소를 선언한다.
..
}
애노테이션의 요소
애노테이션 내에 선언된 메소드를 '애노테이션의 요소(element)'라고 하며, 아래에 선언된 TestInfo애노테이션은 다섯 개의 요소를 갖는다.
enum TestType { FIRST, FINAL }
@interface TestInfo {
int count();
String testedBy();
String[] testTools();
TestType testType();
DateTime testDate();
}
@interface Datetime {
String yymmdd();
String hhmmss();
}
애노테이션의 요소는 반환값이 있고 매개변수는 없는 추상 메소드의 형태를 가지며, 상속을 통해 구현하지 않아도 된다. 다만, 애노테이션을 적용할 때 이 요소들의 값을 빠짐없이 지정해주어야 한다. 요소의 이름도 같이 적어주므로 순서는 상관없다.
@TestInfo(
count = 3, testedBy = "Kim",
testTools = {"JUnit", "AutoTester"},
testType = TestType.FIRST,
testDate = @DateTime(yymmdd = "160101", hhmmss = "235959")
)
public class NewClass { ... }
애노테이션의 각 요소는 기본값을 가질 수 있으며, 기본값이 있는 요소는 애노테이션을 적용할 때 값을 지정하지 않으면 기본값이 사용된다.
@interface TestInfo {
int count() default 1;
}
@TestInfo // @TestInfo(count = 1)와 동일
public class NewClass { ... }
애노테이션 요소가 오직 하나뿐이고 이름이 value인 경우, 애노테이션을 적용할 때 요소의 이름을 생략하고 값만 적어도 된다.
@interface TestInfo {
String value();
}
@TestInfo("passed") // @TestInfo(value = "passed")와 동일
public class NewClass { ... }
요소의 타입이 배열인 경우엔 괄호를 사용해서 여러 값을 지정할 수 있으며, 기본값을 지정할 때도 동일한 방식으로 중괄호를 사용할 수 있다. 한편 요소의 타입이 배열일 때도 요소의 이름이 value이면, 요소의 이름을 생략할 수 있다.
@TestInfo(testTools = {"JUnit", "AutoTeaser"})
@TestInfo(testTools = "JUnit")
@TestInfo(testTools = {})
@interface SuppressWarnings {
String[] value();
}
// @SuppressWarnings(value = {"deprecation", "unchecked"})
@SuppressWarnings({"deprecation", "unchecked"})
java.lang.annotation.Annotation
모든 애노테이션의 조상은 Anootation이다. 그러나 애노테이션은 상속이 허용되지 않으므로 명시적으로 Annotation을 조상으로 지정(extends Annotation)할 수 없다.
또한, Annotation은 일반적인 인터페이스로 정의되어 있으므로, 모든 애노테이션 객체에 대해 equals(), hashCode()m tooString()과 같은 메소드를 호출하는 것이 가능하다.
package java.lang.annotation;
public interface Annotation {
boolean equals(Object obj);
int hashCode();
String toString();
Class<? extends Annotation> annotationType();
}
마커 애노테이션
값을 지정할 필요가 없는 경우, 애노테이션의 요소를 하나도 정의하지 않을 수 있다. Serializable이나 Cloneable인터페이스처럼 요소가 하나도 정의되지 않은 애노테이션을 마커 애노테이션이라고 한다.
애노테이션 요소의 규칙
애노테이션의 요소를 선언할 때 반드시 다음의 규칙을 지켜야 한다.
- 요소의 타입은 기본형, String, enum, 애노테이션, Class만 허용된다.
- ( )안에 매개변수를 선언할 수 없다.
- 예외를 선언할 수 없다.
- 요소를 타입 매개변수로 정의할 수 없다.
참고
자바의 정석(남궁성 저)
'프로그래밍 언어 > Java + Kotlin' 카테고리의 다른 글
[Java] 스터디 14주차: 제네릭 (0) | 2021.09.07 |
---|---|
[Java] 스터디 13주차: I/O (0) | 2021.09.02 |
[Java] 스터디 11주차: Enum (0) | 2021.08.18 |
[Java] 스터디 10주차: 쓰레드 (0) | 2021.08.17 |
[Java] 스터디 9주차: 예외처리 (0) | 2021.07.29 |