Java의 정석 의 책을 읽고 정리한 내용입니다.
comment
)처럼 프로그래밍 언어에 영향을 미치지 않으면서도 다른 프로그램에게 유용한 정보를 제공할 수 있다는 장점이 있다.
💡 참고
애너테이션(annotation
)의 뜻은 주석, 주해, 메모이다.
✔️ @Test
@Test // 이 메서드가 테스트 대상임을 테스트 프로그램에게 알린다.
public void method() {
...
}
@Test
는 이 메서드를 테스트해야 한다는 것을 테스트 프로그램에게 알리는 역할을 할 뿐,
💡 참고
JDK에서 제공하는 애너테이션java.lang.annotaion
패키지에 포함되어 있다.
일부는
메타 애너테이션
으로 애너테이션을 정의하는데 사용되는 애너테이션의 애너테이션이다.
애너테이션 | 설명 |
---|---|
Override | 컴파일러에게 오버라이딩하는 메서드라는 것을 알린다. |
Deprecated | 앞으로 사용하지 않을 것을 권장하는 대상에 붙인다. |
SuppressWarnings | 컴파일러의 특정 경고메시지가 나타나지 않게 해준다. |
SafeVarargs | 지네릭스 타입의 가변인자에 사용한다.(JDK1.7) |
FunctionalInterface | 함수형 인터페이스라는 것을 알린다.(JDK1.8) |
Native | native메서드에서 참조되는 상수 앞에 붙인다.(JDK1.8) |
Target* | 애너테이션이 적용가능한 대상을 지정하는데 사용한다. |
Documented* | 애너테이션 정보가 javadoc으로 작성된 문서에 포함되게 한다. |
Inherited* | 애너테잇녀이 자손 클래스에 상속되도록 한다. |
Retention* | |
애너테이션이 유지되는 범위를 지정하는데 사용한다. | |
Repeatable* | 애너테이션을 반복해서 적용할 수 있게 한다.(JDK1.8) |
*
가 붙은 것은 메타 애너테이션
✔️@Override
메서드 앞에서만 붙일 수 있는 애너테이션으로, 조상의 메서드를 오버라이딩하는 것이라는 걸 컴파일러에게 알려주는 역할을 한다.
✔️ Deprecated
앞으로 사용하지 않을 것을 권장하는 대상에 붙인다.
✔️ @FunctionalInterface
@FunctionalInterface
애너테이션을 붙이면 컴파일러가 '함수형 인페이스'를 올바르게 선언했는지 확인하고, 잘못된 경우 에러를 발생시킨다.- 함수형 인터페이스를 선언할 때는 이 애너테이션을 반드시 붙이는 것이 좋다.
- 함수형 인터페이스는 추상 메서드가 하나뿐이어야 한다는 제약이 있다.
✔️ @SuppressWarnings
- 컴파일러가 보여주는 경고메세지가 나타나지 않게 억제해준다.
- 묵인해야하는 경고가 발생하는 대상에 반드시
@SuppressWarnings
를 붙여서 컴파일 후에 어떤 경고 메세지도 나타나지 않게 해야한다.
주로 사용되는 것 : deprecation
, unchecked
, rawtypes
, varargs
deprecation
은 앞에서 살펴본 것과 같이 @Deprecated
가 붙은 대상을 사용해서 발생하는 경고를 억제할 때 사용한다.unchecked
는 지네릭스로 타입을 지정하지 않았을 때 발생하는 경고를 억제할 때 사용한다.rawtypes
는 지네릭스를 사용하지 않아서 발생하는 경고를 억제할 때 사용한다.varargs
는 가변인자의 타입이 지네릭 타입일 때 발생하는 경고를 억제할 때 사용한다.@SuppressWarnings("unchecked") // 지네릭스와 관련된 경고를 억제한다.
ArrayList list = new ArrayList(); // 지네릭 타입을 지정하지 않았다.
list.add(obj); // 여기서 경고가 발생한다.
()
안에 문자열로 지정하면 된다.
✔️ @SafeVarags
메서드에 선언된 가변인자의 타입이 non-reifiable
타입의 경우 : 해당 메서드를 선언하는 부분과 호출하는 부분에서 unchecked
경고가 발생한다.
해당 코드에 문제가 없다면 이 경고를 억제하기 위해서 @SafeVarargs
를 사용해야 한다.
@SafeVarags 애너테이션은 static
이나 final
이 붙은 메서드와 생성자에만 붙일 수 있다.
즉, 오버라이드될 수 있는 메서드에는 사용할 수 없다는 뜻이다.
컴파일 후에도 제거되지 않는 타입을 reifiable
타입
제거되는 타입을 non-reifiable
타입
지네릭 타입들 대부분은 컴파일 시에 제거되므로 non-reifiable
타입이다.
💡 참고
reifiable
은re(다시)
+-ify(~화 하다)
+-able(~할 수 있는)
의 합성어로 직역하면,
다시 ~화 할 수 있는
이라는 뜻이다. 컴파일 후에도 타입정보가 유지되면reifiable
타입이다.
@SafeVarargs
대신, @SuppressWarnings("unchecked")
로 경고를 억제하려면, 메서드 선언뿐만 아니라 메서드가 호출되는 곳에도 애너테이션을 붙여야 한다.@SafeVarargs // 'unchecked' 경고를 억제한다.
@SuppressWarnings("varargs") // 'varargs" 경고를 억제한다.
public static <T> List<T> asList(T... a) {
return new ArrayList<>(a);
}
@SafeVarargs
로 unchecked
경고는 억제할 수 있지만, varargs
경고는 억제할 수 없기 때문에 습관적으로 @SafeVarargs
와 @SuppressWarnings("varargs")
를 같이 붙인다.
애너테이션에 붙이는 애너테이션으로 애너테이션을 정의할 때 애너테이션의 적용대상(target)이나 유지기간(retention)등을 지정하는데 사용된다.
💡 참고
- 메타 애너테이션은
java.lang.annotation
패키지에 포함되어 있다.
✔️ @Target
애너테이션이 적용가능한 대상을 지정하는데 사용된다.
@Targer
으로 지정할 수 있는 애너테이션 적용대상의 종류
대상 타입 | 의미 |
---|---|
ANNOTATION_TYPE | 애너테이션 |
CONSTRUCTOR | 생성자 |
FIELD | 필드(멤버변수,enum상수) |
LOCAL_VARIABLE | 지역변수 |
METHOD | 메서드 |
PACKAGE | 패키지 |
PARAMETER | 매개변수 |
TYPE | 타입(클래스, 인터페이스, enum) |
TYPE_PARAMETER | 타입 매개변수(JDK1.8) |
TYPE_USE | 타입이 사용되는 모든 곳(JDK1.8) |
TYPE
은 타입을 선언할 때, 애너테이션을 붙일 수 있다는 뜻이고, TYPE_USE
는 해당 타입의 변수를 선언할 때 붙일 수 있다는 뜻이다.FIELD
는 기본형, 그리고 TYPE_USE
는 참조형에 사용된다는 점에 주의하자!import static java.lang.annotation.ElementType.*;
@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
애너테이션이 유지(
retetnion
)되는 기간을 지정하는데 사용된다.
애너테이션 유지정책(retention policy)의 종류
유지 정책 | 의미 |
---|---|
SOURCE | 소스 파일에만 존재, 클래스 파일에는 존재하지 않음 |
CLASS | 클래스 파일에 존재, 실행시에 사용불가. 기본값 |
RUNTIME | 클래스 파일에 존재. 실행시에 사용가능함 |
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public@interface Override{}
@Override
나 @SuppressWarnings
처럼 컴파일러가 사용하는 애너테이션은 유지 정책이 SOURCE
다.
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface FunctionalInterface {}
RUNTIME
으로 하면, 실행 시에 리플렉션(reflection)
을 통해서 클래스 파일에 저장된 애너테이션의 정보를 읽어서 처리할 수 있다.@FunctionalInterface
는 @Override
처럼 컴파일러가 체크해주는 애너테이션이지만, 실행 시에도 사용되므로 유지 정책이 RUNTIME
으로 되어 있다.
💡 참고
지역 변수에 붙은 애너테이션은 컴파일러만 인식할 수 있으므로, 유지정책이RUNTIME
인 애너테이션을 지역변수에 붙여도 실행 시에는 인식되지 않는다.
✔️ @Documented
javadoc
으로 작성한 문서에 포함되도록 한다.@Override
와 @SuppressWarnings
를 제외하고 모두 이 메타 애너테이션이 붙어있다.@Documented
@Retentiond(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public@interface FunctionalInterface{}
✔️ @Inherited
@Inherited
가 붙은 애너테이션을 조상 클래스에 붙이면, 자손 클래스도 이 애너테이션이 붙은 것과 같이 인식된다.@Inherited // @SupperAnno가 자손까지 영향을 미치게
@Interface SupperAnno {}
@SuperAnno
class Parent {}
class Child extends Parent {} // Child에 애너테이션이 붙은 것으로 인식한다.
Child
클래스는 애너테이션이 붙지 않았지만, 조상인 Parent
클래스에 붙은 @SuperAnno
가 상속되서 Child
클래스에도 @SupperAnno
가 붙은 것처럼 인식된다.
✔️ @Repeatable
@Repeatable(ToDos.class) // ToDo애너테이션을 여러 번 반복해서 쓸 수 있게 한다.
@interface ToDO {
String value();
}
@ToDo
라는 애너테이션이 위 코드처럼 정의되었을 때,MyClass
클래스에 @ToDo
를 여러 번 붙이는 것이 가능하다.@ToDo("delete test codes.")
@ToDo("override inherited methods")
class MyClass {
...
}
@interface ToDos { // 여러 개의 ToDo애너테이션을 담을 컨테이너 애너테이션 ToDos
ToDo[] value(); // ToDo 애너테이션 배열타입의 요소를 선언, 이름이 반드시 value여야 한다.
}
@Repeatable (ToDos.class) // 괄호 안에 컨테이너 애너테이션을 지정해 줘야 한다.
@interface ToDo {
String value();
}
✔️ @Native
native method
)에 의해 참조되는 상수 필드(constant field)
에 붙이는 애너테이션이다.@Native public static final long MIN_VALUE = 0x800000000000L;
public class Object {
private static native void registerNatives(); // 네이티브 메서드
static {
registerNatives(); // 네이티브 메서드를 호출한다.
}
protected natvie Object clone() throws CloneNotSupportedException;
public final native Class<?> getClass();
public final native void notify();
public final native void notifyAll();
public final native void wait(long timeout) throws InterruptedException;
public native int hashCode();
...
}
@
기호를 붙이는 것을 제외하면 인터페이스를 정의하는 것과 동일하다.@interface 애너테이션 이름 {
타입 요소이름(); // 애너테이션의 요소를 선언한다.
...
}
@Override
'는 애너테이션이고, Override
는 애너테이션의 타입이다.
✔️ 애너테이션의 요소
애너테이션의 요소(elemnet)
라고 한다.
💡 참고
- 애너테이션에도 인터페이스처럼 상수를 정의할 수 있지만, 디폴트 메서드는 정의할 수 없다.
@interface TestInfo {
int count() default 1; // 기본값을 1로 지정한다.
}
@TestInfo // @TestInfo(count=1)과 동일
public class NewClass {...}
null
을 제외한 모든 리터럴이 가능하다.
@interface TestInfo {
String value();
}
@TestInfo("passed") // @TestInfo(value="passed")와 동일하다.
class NewClass {...}
value
인 경우, 애너테이션을 적용할 때 요소의 이름을 생략하고 값만 적어도 된다.
@interface TestInfo {
String[] testTools();
}
@Test(testTools = {"JUnit", "AuthoTester"}) // 값이 여러 개인 경우
@Test(testTools = "JUnit") // 값이 하나일 때는 괄호 {}생략 가능
@Test(testTools = {} // 값이 없을 때는 괄호{}가 반드시 필요하다.
{}
를 사용해서 여러 개의 값을 지정할 수 있다.
@interface TestInfo {
String[] info() default {"aaa", "bbb"}; // 기본값이 여러 개인 경우. 괄호{}사용
String[] info2() default "ccc"; // 기본값이 하나인 경우, 괄호 생략 가능
}
@TestInfo // @TestInfo(info={"aaa","bbb"}, info2="ccc")와 동일
@TestInfo(info2={}) // @TestInfo(info={"aaa","bbb"}, info2={})와 동일
class NewClass {...}
{}
를 사용할 수 있다.
@interface SuppressWarnings {
String[] value();
}
value
이면, 요소의 이름을 생략할 수 있다.@SuppressWarnings
의 경우, 요소의 타입이 String
배열이고 이름이 value
다.
// @SuppressWarnings(value={"deprecation", "unchecked"})
@SuppressWarnings({"derpecaion", "unchecked")
class newClass {...}
✔️ java.lang.annotation.Annotation
Annotation
이다.package java.lang.annotation;
public interface Annotation { // Annotation자신은 인터페이스다.
boolean equals(Object obj);
int hashCode(0;
String to String();
Class<? extends Annotation> annotationType(); // 애너테이션의 타입을 반환한다.
Annotation
은 애너테이션이 아니라 일반적인 인터페이스로 정의되어 있다!
Class<AnnotaionTest> cls = AnnotationTest.class;
Annotation[] annoArr = AnnotationTest.class.getAnnotations();
for(Annotation a : annoArr) {
System.out.println("toString():"+a.toString());
System.out.println("hashCode():"+a.hashCode());
System.out.println("equals():"+a.equals(a));
System.out.println("annotationType():"+a.annotationType()):
}
equals()
, hashCode()
, toString()
과 같은 메서드를 호출하는 것이 가능하다.
✔️ 마커 애너테이션 Marker Annotation
요소가 하나도 정의되지 않은 애너테이션
@Target(ElemnetType.METHOD)
@Retention(RetetionPolicy.SOURCE)
public @interface Override{} // 마커 애너테이션. 정의된 요소가 하나도 없다.
@Target(ElementType.METHOD)
@Retetion(RetentionPolicy.SOURCE)
public @interface Test {} // 마커 애너테이션. 정의된 요소가 하나도 없다.
✔️ 애너테이션 요소의 규칙
- 요소의 타입은 기본형, String, enum, 애너테이션, Class만 허용된다.
- ()안에 매개변수를 선언할 수 없다.
- 예외를 선언할 수 없다.
- 요소를 타입 매개변수로 정의할 수 없다.
@interface AnnoTest {
int id = 100; // Ok 상수 선언. static final int id = 100;
String major(int i, int j); // 에러. 매개변수를 선언할 수 없음
String minor() throws Exception; // 에러. 예외를 선언할 수 없음.
ArrayList<T> list(); // 에러. 요소의 타입에 타입 매개변수 사용 불가
}
import java.lang.annotation.*;
@Deprecated
@SuppressWarnings("1111") // 유효하지 않은 애너테이션은 무시된다.
@TestInfo(testedBy="aaa", testDate=@DateTime(yymmdd="160101", hhmmss="235959"))
class AnnotationsEx5 {
public static void main(String args[]) {
// AnnotationEx5의 Class객체를 얻는다.
Class<AnnotationEx5> cls = AnnotationEx5. class;
TestInfo anno = (TestInfo)cls.getAnnotation(TestInfo.class);
System.out.println("anno.testedBy()="+anno.testedBy());
System.out.println("anno.testDate().yymmdd()="+anno.testDate().yymmdd());
System.out.println("anoo.testDate().hhmmss()="+anno.testDate().hhmmss());
for(String str : anno.testTools())
System.out.println("testTools="+str);
System.out.println();
// AnnotationEx5에 적용된 모든 애너테이션을 가져온다.
Annotation[] annoArr = cls. getAnnotations();
for(Annotaion a : annoArr)
System.out.println(a);
} // main의 끝
}
@Retention(RetentionPolicy.RUNTIME) // 실행 시에 사용가능하도록 지정한다.
@interface TestInfo {
int count() default 1;
String testedBy();
String[] testTools() default "JUnit";
TestType testType() default TestType.FIRST;
DateTime testDate();
}
@Retention (RetentionPolicy.RUNTIME) // 실행 시에 사용가능하도록 지정한다.
@interface DateTime {
String yymmdd();
String hhmmss();
}
enum TestType {FIRST, FINAL}
anno.testedBy()=aaa
anno.testDate().yymmdd()=160101
anno.testDate().hhmmss()=235959
testTools=JUnit
@java.lang.Deprecated()
@TestInfo(count=1, testType=FIRST, testTools=[JUnit], testedBy=aaa, testDate=@DateTime(yymmdd=160101, hhmmss=235959))
AnnotationEx5
클래스에 적용된 애너테이션을 실행시간에 얻으려면
Class<AnnotationEx5> cls = AnnotationEx5.class;
TestInfo anno = (TestInfo)cls.getAnnotation(TestInfo.class);
와 같이 사용하면 된다.
AnnotationEx5.class
는 클래스 객체를 의미하는 리터럴이다.
TestInfo anno = (TestInfo)cls.getAnnotation(TestInfo.class);
System.out.println("anno.testedBy()="+anno.testedBy());
// AnnotationEx5에 적용된 모든 애너테이션을 가져온다.
Annotation[] annoArr = cls.getAnnotations();
getAnnotation()
이라는 메서드에 매개변수로 정보를 얻고자하는 애너테이션을 지정해주거나 getAnnotations()
로 모든 애너테이션을 배열로 받아올 수 있다.