컴파일러를 비롯한 다른 프로그램들을 위해 소스코드 안에 약속된 형식으로 정보를 표시하는 것.
예를 들어 어떤 메서드 위에 @Test
라는 애너테이션을 붙여놓으면, 테스트 프로그램에게 이 메서드를 테스트해야한다는 것을 알려줄 수 있음
@Test
public void method() {
...
}
외부 프로그램이 아닌 JDK
에서 제공되는 표준 애너테이션은 자바 컴파일러를 위한 용도.
메서드 앞에만 붙일 수 있음.
이 메서드가 조상의 메서드를 오버라이딩한 것이라는 의미.
애너테이션 없이 오버라이딩할 때 메서드 이름을 잘못 적는 실수를 하면, 컴파일러는 이 실수를 잡아주지 못하는데,
class Parent {
void parentMethod() {
...
}
}
class Child extends Parent {
void parentmethod() { // 잘못된 오버라이딩인데, 에러 발생 없음
...
}
}
@Override
라고 명시해주면 조상 클래스에 해당 이름의 메서드가 있는지 확인하고 없는 경우 에러 메시지를 출력해 실수를 바로잡을 수 있게 해줌.
class Child extends Parent {
@Override
void parentmethod() { // 애너테이션에 의해 에러 발생
...
}
}
JDK
버전이 업데이트 될 때, 낡은 기능을 대체할 것이 추가되어도 하위 호환성을 위해 기존의 기능을 제거하지 않고 남겨둠.
더 이상 사용되지 않는 필드나 메서드에 @Deprecated
을 붙여서 이 기능이 더 이상 쓰이지 않으니, 더 개선된 것을 사용하라고 권장하고 있음.
ex) Java API에서 Date
클래스에 정의된 getDate()
메서드의 설명은 아래와 같고,
getDate()
Deprecated.
As of JDK version 1.1, replaced by Calendar.get(Calendar.DAY_OF_MONTH).
Calendar
클래스의get(Calendar.DAY_OF_MONTH)
를 사용하도록 권장하고 있음.
실제 코드에도 애너테이션이 달려있음
@Deprecated
public int getDate() {
...
}
함수형 인터페이스 선언 시, 컴파일러가 올바로 선언했는지 확인해줌.
함수형 인터페이스에는 추상 메서드가 오직 1개만 있어야함
@FunctionalInterface
public interface Shareable {
public abstract void share();
public abstract void copy();
}
java: Unexpected @FunctionalInterface annotation
Shareable is not a functional interface
multiple non-overriding abstract methods found in interface Shareable
컴파일러의 경고 메시지를 억제(suppress)해줌.
특정 대상에서 경고가 발생하는 것을 알고도 묵인해야하는 경우, @SuppressWarnings
를 붙이면 컴파일 시 대상으로부터 아무런 경고 메시지도 뜨지 않게 할 수 있음.
억제할 수 있는 경고 메시지의 종류
"deprecation": @Deprecated
가 붙은 대상을 사용해서 발생하는 경고
"unchecked": 제네릭 타입을 지정하지 않았을 때 발생하는 경고
"rawtypes": 제네릭을 사용하지 않고 원시 타입으로 사용할 때 발생하는 경고
"varargs": 가변 인자의 타입이 제네릭 타입일 때 발생하는 경고
...등이 있고, 이들 중 하나를 애너테이션 뒤의 괄호에 넣어 사용
@SuppressWarnings("unchecked")
ArrayList list = new ArrayList(); // 원시 타입으로 선언
list.add(obj); // 메서드를 호출하면서 unchecked 경고 발생
add
메서드를 호출해서 원시 타입인list
에 무슨 타입인지 모르는obj
라는 요소를 추가하려고 시도하면서 "unchecked call ..." 경고가 발생.하지만 애너테이션으로 막았기 때문에 해당 경고는 표시되지 않음
2개 이상의 경고를 억제할 때는 {}
안에 넣음
@SuppressWarnings({"deprecation", "unchecked"})
JDK
가 계속 업데이트 되면서 새로운 종류의 경고가 나오게 될텐데, 이를 파악하기 위해서는 컴파일시 "-Xlint" 옵션을 붙임.
나타난 경고의 내용에서
[]
안에 있는 것이 경고의 종류
메서드의 매개변수가 가변인자이고, 그 타입이 non-reifiable 타입인 경우,
해당 메서드를 선언하는 곳과 호출하는 곳에서 모두 "unchecked" 경고가 발생함.
non-reifiable 타입: 컴파일 이후에 제거되는 타입
제네릭 타입처럼 컴파일 타임에만 사용되고 컴파일 이후에는 남아있지 않다는 뜻
그러나 프로그래머가 코드에 문제가 없다고 확신할 때, "unchecked" 경고를 억제하기 위해 @SafeVarargs
를 사용할 수 있음.
java.util.Arrays
클래스의 asList()
메서드가 좋은 예시.
public static <T> List<T> asList(T... item) {
return new ArrayList<T>(item);
}
📌 [주의] 반환되는
ArrayList
는Arrays
클래스의 내부 클래스이므로 일반적으로 쓰는ArrayList
와는 다름.
매개 변수로 넘겨 받은 값들(가변 인자)로 배열을 만든 뒤, 내부 클래스인 ArrayList
의 인스턴스에 담아 반환하는 과정.
문제는 이 가변 인자가 제네릭 타입 T
라는 것인데, 메서드에 선언된 원시 타입 T
는 컴파일 시 Object
로 바뀌면서 사라짐.
가변 인자는 여러 개의 인자를 뜻하므로, 결국 매개 변수 타입이 Object[]
가 되는데
public static List asList(Object[] item) {
return new ArrayList<T>(item);
}
Object[]
에는 온갖 타입의 객체가 다 들어갈 수 있으므로, 이 짬뽕 같은 배열을 가지고서 T
타입 원소만을 가진 ArrayList
로 만드는 것은 컴파일러 입장에서는 위험한 시도임.
그러나 실제로 메서드가 호출되는 부분에서는 asList()
의 인자가 T
타입(혹은 T
타입 요소로 이루어진 배열)이 맞는지 컴파일러가 확인할 것이기 때문에 문제가 되지 않음.
선언할 당시에는 컴파일러가 호출 과정을 겪어보지 않았으므로, 나중에 호출 과정에서 자기가 직접 메서드의 타입 체킹을 하게 될 거라는 건 모르고 있음.
따라서 이 메서드의 가변 인자가 type-safe 하다고 컴파일러에게 알리기 위해 @SafeVarargs
를 붙임
이렇게 하면 선언하는 곳과 호출하는 곳 양쪽 모두 "unchecked" 경고가 억제되면서 경고창에 아무것도 뜨지 않게 되지만,
@SuppressWarnings("unchecked")
는 사용한 곳에서만 억제가 되므로 양쪽에 모두 붙여놔야 같은 효과를 낼 수 있음
-Xlint 옵션으로 컴파일 해보면 "varargs" 경고가 남아있는 것을 볼 수 있는데, 이마저도 깔끔히 없애주기 위해 의례적으로 @SuppressWarnings("varargs")
도 함께 사용해줌.
@SafeVarargs
@SuppressWarnings("varargs")
public static <T> List<T> asList(T... item) {
return new ArrayList<T>(item);
}
애너테이션을 위한 애너테이션.
애너테이션을 정의할 때 적용 대상과 유지 기간을 지정하기 위해 사용.
애너테이션을 적용할 수 있는 대상을 지정
이러한 대상들은 java.lang.annotation.ElementType
열거형에 미리 정의되어 있음
예를 들어 @SuppressWarnings
가 붙을 수 있는 대상은 다음과 같이 지정
import static java.lang.annotation.ElementType.*;
@Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE})
@Retention(RetentionPolicy.SOURCE)
public @interface SuppressWarnings {
String[] value();
}
import static java.lang.annotation.ElementType.*;
@Target({FIELD, TYPE, TYPE_USE})
public @interface MyAnnotation { ... }
@MyAnnotation // TYPE
class Dummy {
@MyAnnotation // FIELD
int num;
@MyAnnotation // TYPE_USE
String str;
}
📌 [주의] FIELD는 기본형에, TYPE_USE는 참조형에 쓰임
애너테이션이 유지되는 기간을 지정하는데에 사용
3가지 종류의 retention policy가 존재
이름 | 의미 |
---|---|
SOURCE | 소스 파일(.java)에만 존재, 클래스 파일(.class)에는 존재하지 않음 |
CLASS | 클래스 파일에 존재, 실행 시에는 사용 불가 (default) |
RUNTIME | 클래스 파일에 존재, 실행 시에 사용 가능 |
SOURCE
를 사용하면 애너테이션 정보가 컴파일 과정에서 사라지므로, 컴파일러만이 사용하는 애너테이션에 붙임.
직접 컴파일러를 작성할 것이 아니라면 이 정책을 사용할 일은 없음
ex) @Override
의 정의
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public @interface Override {}
CLASS
를 사용하면 애너테이션 정보가 컴파일 이후의 클래스 파일까지는 살아있지만, 결국 클래스 파일이 JVM
의 클래스 로더에 로딩될 때는 무시되기 때문에 런타임에는 애너테이션 정보가 남아있지 않음.
그래서 RetentionPolicy의 기본값인데도 실제로는 잘 사용되지 않음
RUNTIME
을 사용하면 런타임에도 Reflection을 통해 클래스 파일에 저장된 애너테이션 정보를 읽어와 처리할 수 있음.
Reflection: 구체적인 클래스 타입을 알지 못해도 해당 클래스의 타입, 변수, 메서드에 접근할 수 있게 해주는 Java API
"당장은 어떤 타입의 클래스인지 몰라도 일단 넘어가고, 런타임에는 클래스 저장소를 뒤져서 해당 클래스에 대한 정보를 얻을 수 있으니 그 때 제대로 사용하라"는 뜻인 것 같음. (뇌피셜)
ex) @FunctionalInterface
의 정의
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface FunctionalInterface {}
@FunctionalInterface
는 컴파일러가 체크해주는 애너테이션이면서 런타임에도 사용되므로 유지 정책RUNTIME
필요
애너테이션 정보가 javadoc으로 작성한 문서에 포함되도록 함.
@Override
와 @SuppressWarnings
를 제외한 모든 애너테이션에 붙어있는 메타 애너테이션.
애너테이션이 자손 클래스에 상속되도록 함.
@Inherited
를 붙인 애너테이션을 조상 클래스에 붙이면, 자손 클래스에도 애너테이션이 똑같이 적용됨.
@Inherited
@interface SuperAnnotation {}
@SuperAnnotation
class Parent {}
// @SuperAnnotation 을 달아놓진 않았지만 달아놓은 것과 마찬가지로 인식됨
class Child extends Parent {}
하나의 애너테이션을 여러번 붙일 수 있게함.
@Repeatable(ToDos.class)
@interface ToDo {
String value();
}
@ToDo("delete test codes")
@ToDo("override inherited methods")
class MyClass { ... }
여기서 @Repeatable
옆의 괄호에 들어간 ToDos.class
는 여러번 반복 사용될 수 있는 @ToDo
애너테이션을 묶어서 다루게 해주는 컨테이너 애너테이션. 반드시 정의되어야 함
@interface ToDos {
ToDo[] value();
}
Native Method에 의해 참조되는 상수 필드에 붙이는 애너테이션.
Native Method:
JVM
이 설치된 OS의 자체 메서드Native Method는
Java
언어로 정의되어 있어서 호출도Java
의 일반적인 메서드 호출 방식과 같지만, 실제로 호출되는 것은 OS의 메서드임.(이게 가능하게끔
Java
로 정의해놓은 Native Method와 실제 OS의 메서드 사이의 중간다리 역할을 해주는JNI
(Java Native Interface)라는 개념이 있다고함.)
ex) java.lang.Long
클래스에 정의된 상수 MIN_VALUE
@Native public static final long MIN_VALUE = 0x800000000000000L;
직접 애너테이션을 만드는 방법은 아래와 같음
@interface 애너테이션이름 {
타입 요소이름(); // 애너테이션의 요소 선언
...
}
애너테이션의 요소(element) == 애너테이션 내에 선언된 메서드
애너테이션 요소의 규칙:
요소의 타입은 기본형, String, enum, 애너테이션, Class만 허용
매개 변수는 선언 불가
예외 선언 불가
타입 매개변수 T
사용 불가
ex) @TestInfo
애너테이션을 선언해보자.
@interface TestInfo {
int count();
String tester();
String[] testTools();
TestType testType(); // TestType 열거형 타입
DateTime testDate(); // DateTime 애너테이션 타입
}
@interface DateTime {
String yymmdd();
String hhmmss();
}
enum TestType {
FIRST, FINAL
}
testDate()
에서처럼 애너테이션 요소의 반환타입으로 다른 애너테이션을 사용할 수도 있음
애너테이션 요소는 반환값만 있고, 매개변수는 없음.
상속을 통한 구현 의무가 없음.
애너테이션을 적용할 때는 요소의 값을 전부 지정해주어야함. (순서는 무관)
@TestInfo(
count = 3, tester="Andy",
testTools = {"JUnit", "AutoTester"},
testType = TestType.FIRST,
testDate = @DateTime(yymmdd = "220206", hhmmss = "171030")
)
public class Dummy { ... }
요소에 기본값을 설정해놓으면, 요소의 값이 지정되지 않았을 때 기본값이 사용됨.
단, 기본값으로
null
은 사용 불가
@interface TestCount {
int count() default 1;
}
@TestCount // count = 1 로 지정한것과 동일
public class Dummy { ... }
애너테이션의 요소가 1개 뿐이고, 이름이 value
인 경우에는 애너테이션 적용 시 요소의 이름을 생략하고 값만 적어도 됨.
요소 타입이 배열이어도 이름이
value
면 이름 생략 가능
@interface Tester {
String value();
}
@Tester("Andy") // value = "Andy" 로 작성한 것과 동일
public class Dummy { ... }
요소의 반환타입이 배열인 경우네는 애너테이션 적용 시 {}
로 묶어서 값을 지정함.
@interface TestTools {
String[] toolName();
}
@TestTools(toolName = {"JUnit", "AutoTester"})
public class Dummy { ... }
📌 [주의] 값을 1개만 넣을 경우에는
{}
생략 가능. 값을 넣지 않을 때는{}
생략 불가@TestTools(toolName = "JUnit") // 값이 1개 @TestTools(toolName = {}) // 값이 없음
기본값을 지정할 때도 개수가 여러개면 {}
로 묶음.
모든 애너테이션의 조상인 Annotation
은 상속이 허용되지 않음.
@interface TestInfo extends Annotation { // (X) 상속 불가
...
}
Annotation
은 일반 interface로 선언되어있음.
public interface Annotation {
boolean equals(Object o);
int hashCode();
String toString();
Class<? extends Annotation> annotationType(); // 애너테이션의 타입을 반환해줌
}
조상이 이렇게 생겼으므로, 모든 애너테이션의 객체는 equals()
, hashCode()
, toString()
호출 가능.
@DataAmount
@Threshold
class AnnotationTest {}
class Test {
public static void main(String[] args) {
Class<AnnotationTest> cls = AnnotationTest.class; // 클래스 객체
Annotation[] annoArr = cls.getAnnotations(); // 모든 애너테이션을 배열로 얻어옴
// Annotation anno = cls.getAnnotation(DataAmount.class);
// 정보를 얻고자 하는 애너테이션의 클래스 객체를 매개변수로 지정
for (Annotation a : annoArr) {
System.out.println(a.toString());
System.out.println(a.hashCode());
System.out.println(a.equals(a));
System.out.println(a.annotationType());
}
}
}
실행 결과)
@jdk.jfr.DataAmount("BYTES")
1280831300
true
interface jdk.jfr.DataAmount
@jdk.jfr.Threshold("0 ns")
1334472890
true
interface jdk.jfr.Threshold
이처럼 런타임에 애너테이션 정보를 얻고자 할 때는 클래스 객체를 통해
getAnnotation()
이나getAnnotations()
메서드를 사용함.
getAnnotation()
: 매개변수에 정보를 얻고자 하는 애너테이션을 지정해야함.
getAnnotations()
: 별도의 매개변수 지정 없이 모든 애너테이션을 배열로 받아옴.
요소가 하나도 없는 비어있는 애너테이션
ex) @Override
는 마커 애너테이션임.
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public @interface Override {} // 요소가 없음