주석처럼 프로그래밍 언어에 영향을 미치지 않으며, 유용한 정보를 제공한다. 애노테이션은 프로그램에 대한 추가적인 정보를 제공하는데 사용한다.
(Java doc) - 메타데이터의 한 형태인 주석은 프로그램 내에 일부가 아닌 프로그램에 대한 데이터를 제공한다.
@Test // 밑에 선언된 해당 메소드가 테스트 대상임을 프로그램에게 알리는 역할을 한다.
public void method() {
//...
}
탄생 이유는 프로그래머에게 그들의 코드에 대한 메타데이터를 자신의 코드에 직접적으로 기술할수있는것을 제공하기위함이다. 어노테이션이 만들어지기전에 프로그래머가 자신의 코드를 기술하는 방법은 transient 키워드를 사용한다던가, 주석(comments) 를 통하여, 인터페이스를 이용등등 중구난방이었다.그리고 여러타입의 어플리케이션에서 코드를 기술하는 메커니즘은 주로 XML 이 사용되어졌는데 이것은 그리 좋은 방법은 아닌게 코드와 XML (XML 은 코드가 아니다) 사이에 디커플링이 발생되고 이것은 어플리케이션을 유지보수하기 힘들게 한다.
출처: https://hamait.tistory.com/315 [HAMA 블로그]
데이터에 대한 데이터이며, 간단히 정리하자면 어떤 목적을 가지고 만들어진 데이터라고 표현할 수 있다.
애노테이션은 사용할 때 여러 가지의 용도가 있다.
컴파일러를 위한 정보 -- 애노테이션을 사용하여 컴파일러에게 오류를 감지하거나 경고를 차단할 수 있게끔 해준다.
컴파일 시간 및 배포 시간 처리 -- 소프트웨어 도구(IDE?)는 주석 정보를 처리하여 코드, XML 파일 등을 생성할 수 있게 해준다.
런타임 처리 -- 일부 주석은 런타임에 검사할 수 있다.
커스텀 애노테이션이라고 부르며 이와 관련된 소스는 java.lang.annotation 패키지에 속해있다. 커스텀 애노테이션을 정의하려면 interface라는 키워드를 쓰고 그 앞에 @를 붙여주면 애노테이션이 정의된다. 공백을 넣어 @ interface도 가능하다고는 하지만, 표준 스타일을 맞추기 위해서 공백없이 붙여주는게 좋은 방법이다.
@interface MyAnnotation {
// 애노테이션의 요소를 선언한다.
}
햄버거를 나타내고 두 개의 요소를 가지고 있는 애노테이션을 테스트로 만들어보면
@interface Hamburger {
String patty(); // 패티를 의미하는 요소
String source(); // 소스를 의미하는 요소
}
인터페이스 공부할 때 인터페이스에서 추상 메소드를 선언할 수 있는 것처럼, 애노테이션도 추상 메소드로 선언한다. 추상 메소드로 선언하지만 구현할 필요는 없다. 대신 사용하는 쪽에서 애노테이션에 있는 요소들의 값들을 다 넣어줘야한다. 이때 순서는 상관없다.
@interface BicMacAnnotation {
int pickle();
String madeBy();
String bread();
String[] pattys();
String source();
}
@BicMacAnnotation(pickle = 3, madeBy = "JeongSeokLee", bread = "BicMacBurn", source = "BicMacSource", pattys = {"10:1 patty", "10:1 patty"})
public class CustomAnnotationTest {
}
@interface MyAnnotation {
int count() default 1;
}
@MyAnnotation // @MyAnnotation(count=1)과 동일한 결과이다.
public class MyClass {
//...
}
@interface MyAnnotation {
String value();
}
@MyAnnotation("test") // @MyAnnotation(value="test")과 동일한 결과이다.
public class MyClass {
//...
}
@interface MyAnnotationArr {
String[] texts();
}
@MyAnnotation(texts={"test", "test1"})
@MyAnnotation(texts="test") // 값이 하나있을 땐 {}괄호를 작성 X
@MyAnnotation({}) // 값이 없을 땐 {}괄호가 반드시 있어야 한다.
public class MyClass {
//...
}
java.lang.annotation.Annotation 인터페이스가 모든 애노테이션의 조상이다.
package java.lang.annotation;
/**
* The common interface extended by all annotation types. Note that an
* interface that manually extends this one does <i>not</i> define
* an annotation type. Also note that this interface does not itself
* define an annotation type.
*
* More information about annotation types can be found in section 9.6 of
* <cite>The Java™ Language Specification</cite>.
*
* The {@link java.lang.reflect.AnnotatedElement} interface discusses
* compatibility concerns when evolving an annotation type from being
* non-repeatable to being repeatable.
*
* @author Josh Bloch
* @since 1.5
*/
public interface Annotation {
/**
* Returns true if the specified object represents an annotation
* that is logically equivalent to this one. In other words,
* returns true if the specified object is an instance of the same
* annotation type as this instance, all of whose members are equal
* to the corresponding member of this annotation, as defined below:
* <ul>
* <li>Two corresponding primitive typed members whose values are
* <tt>x</tt> and <tt>y</tt> are considered equal if <tt>x == y</tt>,
* unless their type is <tt>float</tt> or <tt>double</tt>.
*
* <li>Two corresponding <tt>float</tt> members whose values
* are <tt>x</tt> and <tt>y</tt> are considered equal if
* <tt>Float.valueOf(x).equals(Float.valueOf(y))</tt>.
* (Unlike the <tt>==</tt> operator, NaN is considered equal
* to itself, and <tt>0.0f</tt> unequal to <tt>-0.0f</tt>.)
*
* <li>Two corresponding <tt>double</tt> members whose values
* are <tt>x</tt> and <tt>y</tt> are considered equal if
* <tt>Double.valueOf(x).equals(Double.valueOf(y))</tt>.
* (Unlike the <tt>==</tt> operator, NaN is considered equal
* to itself, and <tt>0.0</tt> unequal to <tt>-0.0</tt>.)
*
* <li>Two corresponding <tt>String</tt>, <tt>Class</tt>, enum, or
* annotation typed members whose values are <tt>x</tt> and <tt>y</tt>
* are considered equal if <tt>x.equals(y)</tt>. (Note that this
* definition is recursive for annotation typed members.)
*
* <li>Two corresponding array typed members <tt>x</tt> and <tt>y</tt>
* are considered equal if <tt>Arrays.equals(x, y)</tt>, for the
* appropriate overloading of {@link java.util.Arrays#equals}.
* </ul>
*
* @return true if the specified object represents an annotation
* that is logically equivalent to this one, otherwise false
*/
boolean equals(Object obj);
/**
* Returns the hash code of this annotation, as defined below:
*
* <p>The hash code of an annotation is the sum of the hash codes
* of its members (including those with default values), as defined
* below:
*
* The hash code of an annotation member is (127 times the hash code
* of the member-name as computed by {@link String#hashCode()}) XOR
* the hash code of the member-value, as defined below:
*
* <p>The hash code of a member-value depends on its type:
* <ul>
* <li>The hash code of a primitive value <tt><i>v</i></tt> is equal to
* <tt><i>WrapperType</i>.valueOf(<i>v</i>).hashCode()</tt>, where
* <tt><i>WrapperType</i></tt> is the wrapper type corresponding
* to the primitive type of <tt><i>v</i></tt> ({@link Byte},
* {@link Character}, {@link Double}, {@link Float}, {@link Integer},
* {@link Long}, {@link Short}, or {@link Boolean}).
*
* <li>The hash code of a string, enum, class, or annotation member-value
I <tt><i>v</i></tt> is computed as by calling
* <tt><i>v</i>.hashCode()</tt>. (In the case of annotation
* member values, this is a recursive definition.)
*
* <li>The hash code of an array member-value is computed by calling
* the appropriate overloading of
* {@link java.util.Arrays#hashCode(long[]) Arrays.hashCode}
* on the value. (There is one overloading for each primitive
* type, and one for object reference types.)
* </ul>
*
* @return the hash code of this annotation
*/
int hashCode();
/**
* Returns a string representation of this annotation. The details
* of the representation are implementation-dependent, but the following
* may be regarded as typical:
* <pre>
* @com.acme.util.Name(first=Alfred, middle=E., last=Neuman)
* </pre>
*
* @return a string representation of this annotation
*/
String toString();
/**
* Returns the annotation type of this annotation.
* @return the annotation type of this annotation
*/
Class<? extends Annotation> annotationType(); //
}
Annotation 인터페이스는 모두 추상 메소드로 이루어져있으며, 갯수도 4개 밖에 없다.
애노테이션의 요소와 관련되서 몇가지 규칙이 있다. (굳이 외울필요는 없지만 알아두면 좋을 법하다)
@interface WrongAnnotation {
int id = 100; // 인터페이스와 동일하게 static final이 숨겨져 있기 때문에 이는 상수로써 사용 가능
String method(int i, int j); // 매개 변수 X
String method2() throws Exception; // 예외 선언 X
ArrayList<T> list(); // 요소의 타입을 매개변수로 정의할 수 없다.
}
애노테이션은 메타데이터의 저장을 위해 클래스처럼 멤버를 가질 수 있는데, 이때 멤버의 갯수에 따라 Marker Annotation, Single Value Annotation, Full Annotation으로 분류할 수 있다.
멤버 변수가 없으며, 단순히 표식으로서 사용되는 애노테이션이다. 컴파일러에게 어떤 의미를 전달한다. 대표적으로 @Override 애노테이션이 Marker Annotation의 예이다.
@Override
@Deprecated
멤버로 단일변수만을 갖는 애노테이션이다. 단일변수 밖에 없기 때문에 (값)만을 명시하여 데이터를 전달할 수 있다.
@TestAnnotation("testing")
멤버를 둘 이상의 변수로 갖는 애노테이션으로, 데이터를 (값=쌍)의 형태로 전달된다.
Example:- @TestAnnotation(owner=”Rahul”, value=”Class Geeks”)
자바에서 몇가지 애노테이션을 기본적으로 제공해준다. 모두 java.lang 패키지에 속하며, 총 5개의 애노테이션이 있다.
오버라이딩을 올바르게 했는지 컴파일러가 체크하게 해주는 애노테이션이다. 개발자가 오버라이딩할 때 메소드이름을 오타내는 경우가 종종 있다.
class Drink {
String makeDrink() {
return "음료 만드는 중 입니다.";
}
}
class Coke extends Drink {
// 부모 클래스의 method()를 오버라이딩할려고 시도했으나 오타가 발생한 경우
// 결국 메소드를 2개를 만든 결과
String makDrink() {
return "콜라를 만드는 중 입니다.";
}
}
오버라이딩할 때는 메소드 선언부 앞에 @Override를 붙이면, 오타가 나더라도 IDE상에서 에러를 쉽게 확인할 수 있다.
앞으로 사용하지 않을 것을 권장하는 필드나 메소드에 붙이고, 사용하면 위험하거나 해당 코드보다 개선된 코드가 존재하기 때문에 개발자에게 사용하지 말아야 하는 것을 알리기 위해 사용한다. @Deprecated의 사용 예를 확인해보기 위해 Date 클래스의 메소드를 몇 개 확인 해봤다.
@Deprecated
public int getDate() {
return normalize().getDayOfMonth();
}
/**
* Sets the day of the month of this <tt>Date</tt> object to the
* specified value. This <tt>Date</tt> object is modified so that
* it represents a point in time within the specified day of the
* month, with the year, month, hour, minute, and second the same
* as before, as interpreted in the local time zone. If the date
* was April 30, for example, and the date is set to 31, then it
* will be treated as if it were on May 1, because April has only
* 30 days.
*
* @param date the day of the month value between 1-31.
* @see java.util.Calendar
* @deprecated As of JDK version 1.1,
* replaced by <code>Calendar.set(Calendar.DAY_OF_MONTH, int date)</code>.
*/
@Deprecated
public void setDate(int date) {
getCalendarDate().setDayOfMonth(date);
}
실제로 getDate(), setDate() 메소드에는 @Deprecated 라는 애노테이션이 떡하니 붙어있다.
어짜피 쓰지말라고 하는거 삭제하면 되는거 아니야??라는 생각이 문뜩 들어서 이유들을 찾아보니 가장 큰 이유는 하위버전과의 호환성 때문이라고 한다. 예전에 만들었던 프로그램들이 최신 버전으로 업데이트 될 때마다 재기능을 하지 못할 경우가 있기 때문에 이를 방지하기 위해 삭제하지 않는다고 한다.
'getDate()'는 depreacted가 되었고 'Date.getDate()'의 결과는 무시된다는 결과를 얻을 수 있다.
직접 컴파일한 후 결과를 찾아보면..결국 안된다는 말이다..
맨 밑줄에 자세한 정보를 얻고 싶으면 -Xlint:deprecation을 사용해서 Recompile 해보라는데..한번 해볼까..?
결국 사용한 메소드가 deprecation 되었다고 자세히 알려줄 뿐이다.
생성자나 메소드의 가변인자 파라미터가 안전하게 사용된다는 것을 나타내기 위해 사용한다. (여기서 가변인자란? String... values와 같은 형태를 가변인자라고 한다.)
public class SafeVarargsTest {
@SafeVarargs
static void test(List<String>... stringLists) {
// 가변인자 파라미터가 안전하게 사용된다는 의미인 것 같다..
}
}
함수형 인터페이스에는 하나의 추상메소드만 가져야 한다는 제약이 있는데, @FunctionalInterface를 붙이면 컴파일러가 올바르게 작성했는지 알려주는 역할을 한다. 즉 이것도 컴파일러를 위한 애노테이션이다.
전에 공부했던 멀티쓰레드 프로그래밍에서 찾았던 함수형 인터페이스가 있었다.
@FunctionalInterface
public interface Runnable {
/**
* When an object implementing interface <code>Runnable</code> is used
* to create a thread, starting the thread causes the object's
* <code>run</code> method to be called in that separately executing
* thread.
* <p>
* The general contract of the method <code>run</code> is that it may
* take any action whatsoever.
*
* @see java.lang.Thread#run()
*/
public abstract void run();
}
이 코드에선 수정할 수 없기 때문에 만약 @FuntionalInterface 애노테이션을 사용했는데도 하나 이상의 추상 메소드를 선언하면 어떤 에러가 발생하는 지 확인해보자
@FunctionalInterface
interface TestInterface {
void method1();
void method2();
}
@Deprecated와는 다르게 컴파일하기도 전에 이미 문제가 있다고 알려주기 때문에 따로 더 분석할 필요도 없다.
이미 인지하고 있는 컴파일러의 경고메세지가 나타나지 않게 제거해줄때 사용한다. 쉽게 말하면 경고를 안뜨게 해준다는 의미이다.
@Deprecated를 공부하면서 사용했던 예제들을 다시 보면 getDate() 메소드는 deprecated 되었기 때문에 사용하지 말라는 경고를 확인했다. 이 상태에서 개발자가 이미 deprecated된 상황을 인지하고 있고, 경고 메세지를 확인하고 싶지 않을 때 이를 호출하는 선언부에 @SuppressWarnings 애노테이션을 사용하면 된다.
╰─$ javac DeprecatedTest.java
Note: DeprecatedTest.java uses or overrides a deprecated API.
Note: Recompile with -Xlint:deprecation for details.
public class SuppessWarningTest {
@SuppressWarnings("deprecation") // 호출하는 선언부에 작성한다.
public static void main(String[] args) {
Date date = new Date();
date.getDate();
}
}
둘 이상의 경고를 동시에 제거하기 위해서는 아래와 같이 작성하면 된다.
@SuppressWarnings({"deprecation", "unchecked", "varargs"});
그럼 이러한 경고를 제거하기 위해 경고 메세지를 어떻게 작성해야 해야하는지 확인할려면 위에서 @Deprecated 에서 공부했었던 -Xlint 옵션을 사용하여 컴파일하면, 경고 메세지를 확인할 수 있다.
DeprecatedTest.java:8: warning: [deprecation] getDate() in Date has been deprecated
date.getDate();
[] 괄호 안에 deprecation 이라고 작성되어있는데 저 메세지가 바로 @SuppressWarnings에서 사용할 수 있는 경고 메세지이다.
커스텀 애노테이션을 작성시 애노테이션을 설명하기 위한 메타 애노테이션이 있다. 메타 애노테이션을 5개가 존재한다.
javadoc으로 작성한 문서를 포함시키려면 @Documented를 붙인다. 여기서 자바 javadoc을 작성한 문서는 /* 으로 시작해서 /로 끝난다.
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 {
}
애노테이션을 적용할 수 있는 대상(위치)를 나타내는 애노테이션이다. 만약 Target에 선언된 대상과 다른 대상에 애노테이션을 적용할 경우 컴파일 에러가 발생한다. 타입으로 enum인 ElementType[]을 받는다.
자바 언어 명세서(Java Language Specification, JLS)
자바 언어의 명세서를 뜻하며, 자바 프로그래밍 언어를 위한 문법과 정상적인/비정상적인 규칙들을 보여준다. 그리고 정상적인 프로그램을 실행하기위한 프로그램 방법들도 보여준다.
@Target(ElementType.FIELD) // 필드에만 MyTarget Annotation이 적용 가능하게끔 선언
public @interface MyTarget {
}
public class TargetAnnotationTest {
@MyTarget String field_variable = "field에 선언된 애노테이션 입니다.";
public static void main(String[] args) {
System.out.println(new TargetAnnotationTest().field_variable);
}
}
Output
field에 선언된 애노테이션 입니다.
Target에 선언된 위치와 다른곳에서 애노테이션을 사용할 경우
public class TargetAnnotationTest {
@MyTarget String field_variable = "field에 선언된 애노테이션 입니다.";
public static void main(String[] args) {
@MyTarget String field_variable = "field에 선언된 애노테이션 입니다.";
System.out.println(new TargetAnnotationTest().field_variable);
}
}
실제로 @MyTarget 애노테이션을 붙힌 변수를 메인 메소드에서 위치하면 컴파일 에러가 발생한다.
이런 경우 ElementType[] 중 하나인 LOCAL_VARIABLE을 사용하면 이 문제를 해결할 수 있다.
public class TargetAnnotationTest {
@MyTarget String field_variable = "field에 선언된 애노테이션 입니다.";
public static void main(String[] args) {
@MyTarget String local_variable = "ElementType에 LOCAL_VARIABLE enum을 추가한 애노테이션 입니다.";
System.out.println(new TargetAnnotationTest().field_variable);
System.out.println(local_variable);
}
}
Output
field에 선언된 애노테이션 입니다.
ElementType에 LOCAL_VARIABLE enum을 추가한 애노테이션 입니다.
애노테이션이 어느 시점까지 유지되는지를 나타낼 수 있다. enum RetentionPolicy에 3가지의 정책이 있다. @Retention 애노테이션을 생략한다면 RetentionPolicy.CLASS가 적용된다.
해당 애노테이션을 적용하면 부모클래스에 선언된 애노테이션이 자식클래스에 상속된다. getAnnotation()를 호출하여 각 클래스별로 적용된 애노테이션을 확인할 수 있다.
실제로 어떻게 작동하는지 확인해보기 위해 @Inherited을 사용하지 않은 애노테이션과, @Inherited을 사용한 애노테이션을 만든다.
@Retention(RetentionPolicy.RUNTIME)
@interface MyInherited {
}
@Inherited
@Retention(RetentionPolicy.RUNTIME)
@interface ChildAnnotation { // @ChildAnnotation가
}
Parent, Child, GrandChild 클래스를 만든다.
@MyInherited
class Parent {
}
@ChildAnnotation
class Child extends Parent{
}
class GrandChild extends Child{
}
실제로 코드를 통해 해당 애노테이션들이 어디에 적용됬는지 확인해보면 @Inherited 애노테이션을 사용하지 않은 @MyInherited 애노테이션은 자기 자신의 정보를 출력하고, 클래스 상속 관계에서 조차 @MyInherited 적용되지 않는다는 걸을 확인할 수 있다.
하지만 @Inherited를 사용한 @ChildAnnotation을 조회해보면, Child, GrandChild에 애노테이션만 출력된다. GrandChild는 선언된 애노테이션이 없지만, 상속받은 Child 애노테이션이 있기 때문에 동일하게 적용된다.
@MyInherited
class Parent {
}
@ChildAnnotation
class Child extends Parent{
}
class GrandChild extends Child{
}
public class InheritedAnnotationTest {
public static void main(String[] args) {
System.out.println("inherited 애노테이션을 사용한 전");
System.out.println("Before inherited A : " + new Parent().getClass().getAnnotation(MyInherited.class));
System.out.println("Before inherited B : " + new Child().getClass().getAnnotation(MyInherited.class));
System.out.println("Before inherited C : " + new GrandChild().getClass().getAnnotation(MyInherited.class));
System.out.println("\n");
System.out.println("inherited 애노테이션을 사용한 후");
System.out.println("After inherited A : " + new Parent().getClass().getAnnotation(ChildAnnotation.class));
System.out.println("After inherited B : " + new Child().getClass().getAnnotation(ChildAnnotation.class));
System.out.println("After inherited C : " + new GrandChild().getClass().getAnnotation(ChildAnnotation.class));
}
}
Output
inherited 애노테이션을 사용한 전
Before inherited A : @annotationtest.MyInherited()
Before inherited B : null
Before inherited C : null
inherited 애노테이션을 사용한 후
After inherited A : null
After inherited B : @annotationtest.ChildAnnotation()
After inherited C : @annotationtest.ChildAnnotation()
동일한 애노테이션을 여러 개 선언할 경우 컴파일 에러가 발생하지만, @Repeatable을 적용하면 여러 개의 동일한 애노테이션을 선언할 수 있다.
동일한 애노테이션을 여러 개 선언할 경우 어떠한 에러가 발생하는지 간단히 테스트 해보면
@Retention(RetentionPolicy.CLASS)
@interface MyRepeatable { // 간단하게 @Retention 애노테이션을 이용한 커스텀 애노테이션 생성
}
@Hamburger("Cheese")
@Hamburger("BicMac")
public class RepeatableAnnotationTest {
public static void main(String[] args) {
}
}
위 코드를 작성하고 나서 바로 아래와 같은 컴파일 에러가 발생한다.
이러한 문제를 해결하기 위해 @Repeatable을 사용하면 위와 같은 문제를 해결할 수 있다. 하지만 이때 중요한 것은 @Repeatable인 @Hamburger를 하나로 묶을 컨테이너 애노테이션을 정의해야 한다.
@Repeatable(Hamburgers.class)
@interface Hamburger {
String value();
}
// 여러 개의 Hamburger 애노테이션을 담을 컨테이너 애너테이션
@interface Hamburgers {
Hamburger[] value(); // Hamburger 애노테이션 배열타입의 요소로 선언 이름이 반드시 value이어야 한다.
}
@Hamburger("Cheese")
@Hamburger("BicMac")
public class RepeatableAnnotationTest {
//TODO...
}
"Annotation Processing" is a hook into the compile process of the java compiler, to analyse the source code for user defined annotations and handle then (by producing compiler errors, compiler warning, emitting source code, byte code ...).
애노테이션 프로세싱은 자바 컴파일러의 컴파일 단계에서, 유저가 정의한 애노테이션의 소스코드를 분석하고 처리하기 위해 사용하는 훅이다. 컴파일 에러나 컴파일경고를 만들어 내거나, 소스코드(.java)와 바이트코드(.class)를 내보내기도 한다.
출처: https://stackoverflow.com/questions/2146104/what-is-annotation-processing-in-java [stackoverflow]
여기서 나오는 '훅'이란? 후킹(Hooking)이라고 불리며 이미 작성되어 있는 코드의 특정 지점을 가로채서 동작 방식에 변화를 주는 일체의 기술이라고 의미른 지닌 용어이다.
커스텀 애노테이션 프로세서를 만드는 예제 중에 팩토리 패턴이라는 말이 문뜩 궁금하기도 했고, 선장님의 예제에서도 MojaFactory()라는 이름이 있었기에 간단히 찾아봤다.
관련된 예제가 선장님 강의가 있는데..정리할 내용이 너무 많다..