티스토리 뷰

728x90

학습할 것 (필수)

  • 애노테이션 정의하는 방법
  • @retention
  • @target
  • @documented
  • 애노테이션 프로세서

애노테이션(Annotation)이란? 정의하는 방법

애노테이션은 클래스나 메서드 등의 선언 시에 @를 사용하는 것을 말한다.

애노테이션은 메타데이터(metadata) 라고 볼 수 있다. 메타데이터란 애플리케이션이 처리해야 할 데이터가 아니라, 컴파일 과정에서 코드를 어떻게 컴파일하고 처리할 것인지를 알려주는 정보이다.

 

어노테이션은 다음 세 가지 용도로 사용된다.

- 컴파일러에게 코드 문법 에러를 체크하도록 정보를 알려주거나

- 컴파일할 때와 설치시의 작업을 지정하거나

- 실행할 때(런타임시) 별도의 처리가 필요할 때  사용한다.

자바의 표준 애노테이션 (Built-in 애노테이션)

@Override

- 메서드 앞에만 붙일 수 있는 애노테이션으로, 조상의 메서드를 오버라이딩하는 것이라는 걸 컴파일러에게 알려주는 역할

- 메서드를 오버라이딩할 때 필수는 아니지만 실수가 나올 수 있으므로 컴파일러가 알 수 있게 붙여주는 것을 권장한다.

class Parent {
    void parentMethod() {
        
    }
}

class Child extends Parent {
    @Override
    void parentMethod() {
        super.parentMethod();
    }
}

@ Deprecated

- 더 이상 사용하지 않는 필드나 메서드에 사용하는 애노테이션, 앞으로 사용하지 않을 것을 권장하는 대상에 붙인다.

/*
* @deprecated As of JDK version 1.1,
* replaced by {@code Calendar.get(Calendar.DAY_OF_MONTH)}.
*/
@Deprecated
public int getDate() {
    return normalize().getDayOfMonth();
}

@FunctionalInterface

- 자바8부터 사용할 수 있는 @FunctionalInterface는 함수형 인터페이스(functional interface)를 선언할 때, 이 애노테이션을 사용한다.

/**
 * Represents a predicate (boolean-valued function) of one argument.
 *
 * <p>This is a <a href="package-summary.html">functional interface</a>
 * whose functional method is {@link #test(Object)}.
 *
 * @param <T> the type of the input to the predicate
 *
 * @since 1.8
 */
@FunctionalInterface
public interface Predicate<T> {

    /**
     * Evaluates this predicate on the given argument.
     *
     * @param t the input argument
     * @return {@code true} if the input argument matches the predicate,
     * otherwise {@code false}
     */
    boolean test(T t);
    
    ...
}

@SuppressWarnings

- 컴파일러가 보여주는 경고 메시지가 나타나지 않게 해 준다.

- 나타나지 않게 할 경고 메시지의 종류는 여러 개 있다. 주로 사용되는 것은 "deprecation", "unchecked", "rawtypes", "varargs"

- "deprecation"은 @Deprecated가 붙은 대상을 사용해서 발생하는 경고

- "unchecked"는 제네릭 타입으로 지정하지 않았을 때 발생하는 경고

- "rawtypes"는 제네릭을 사용하지 않아서 발생하는 경고

- "varargs"는 가변 인자의 타입이 제네릭 타입일 때 발생하는 경고

@Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE, MODULE})
@Retention(RetentionPolicy.SOURCE)
public @interface SuppressWarnings {
    String[] value();
}

//단일로 사용
@SuppressWarnings("unchecekd")

//여러개 사용
@SuppressWarnings({"unchecekd","rawtypes", "varargs"})

@SafeVarargs

 

메타 애노테이션

메타 애노테이션이란 애노테이션에 붙이는 애노테이션으로 애노테이션을 정의할 때 애노테이션의 적용대상(target)이나 유지기간(retention)등을 지정하는 데 사용된다.

@Target

애노테이션이 적용 가능한 대상을 지정하는 데 사용된다.

자바 공부에서는 동떨어지는 이야기이지만 스프링 프레임워크의 @Controller 어노테이션

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Controller {
    @AliasFor(
        annotation = Component.class
    )
    String value() default "";
}

@Target으로 지정할 수 있는 애노테이션 적용대상의 종류

java.lang.annotation.ElementType이라는 열거형에 정의되어 있다.

public enum ElementType {
    /** Class, interface (including annotation type), or enum declaration */
    TYPE, //타입(클래스, 인터페이스, enum, 애노테이션)

    /** Field declaration (includes enum constants) */
    FIELD,	//필드(멤버변수, enum상수)

    /** Method declaration */
    METHOD, //메소드

    /** Formal parameter declaration */
    PARAMETER,	//매개변수

    /** Constructor declaration */
    CONSTRUCTOR, //생성자

    /** Local variable declaration */
    LOCAL_VARIABLE,	//지역변수

    /** Annotation type declaration */
    ANNOTATION_TYPE,	//애노테이션

    /** Package declaration */
    PACKAGE,	//패키지

    /**
     * Type parameter declaration
     *
     * @since 1.8
     */
    TYPE_PARAMETER,	//타입 매개변수(1.8부터추가)

    /**
     * Use of a type
     *
     * @since 1.8
     */
    TYPE_USE,	//타입이 사용되는 모든 곳

    /**
     * Module declaration.
     *
     * @since 9
     */
    MODULE
}

@Retention

애노테이션이 유지(retention)되는 기간을 지정하는 데 사용된다.

java.lang.annotation.RetentionPolicy 라는 열거형에 정의되어 있다.

public enum RetentionPolicy {
    /**
     * Annotations are to be discarded by the compiler.
     */
    //소스 파일에만 존재, 클래스파일에는 존재하지 않는다.
    SOURCE,

    /**
     * Annotations are to be recorded in the class file by the compiler
     * but need not be retained by the VM at run time.  This is the default
     * behavior.
     */
     // 클래스 파일에 존재, 실행시에 사용 불가, 기본값이다.
    CLASS,

    /**
     * Annotations are to be recorded in the class file by the compiler and
     * retained by the VM at run time, so they may be read reflectively.
     *
     * @see java.lang.reflect.AnnotatedElement
     */
     //클래스 파일에 존재, 실행시에 사용가능하다.
    RUNTIME
}

CLASS가 기본값이고 @Retention을 RUNTIME으로 하면 실행 시에 자바의 리플렉션(reflection)을 통해서 클래스 파일에 저장된 애노테이션의 정보를 읽어서 처리할 수 있다.  

자바 8부터 추가된 @FunctionalInterface는 실행 시에도 사용되므로 유지 정책이 RUNTIME으로 되어 있다.

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface FunctionalInterface {}

@Documented

@Documented는 애노테이션에 대한 정보가 javadoc으로 작성한 문서에 포함되도록 한다.

@Inherited

 

애노테이션이 자손 클래스에 상속되도록 한다.

@Inherited가 붙은 애노테이션을 조상 클래스에 붙이면, 자손 클래스도 이 애노테이션이 붙은 것과 같이 인식된다.

@Repeatable

보통은 하나의 대상에 한 종류의 애노테이션을 붙이는데 @Repeatable이 붙은 애노테이션은 여러 번 붙일 수 있다.

@Repeatable	//ToDo 애노테이션을 여러 번 반복해서 쓸 수 있게 한다.
@interface ToDo{
	String value();
}

@ToDo("delete test codes.")
@ToDo("override inherited methods")
class MyClass{
	...
}

@Native

네이티브 메서드(native method)에 의해 참조되는 상수 필드(constant field)에 붙이는 애노테이션

애노테이션 만들어보기

애노테이션을 정의할 때는 @interface를 사용해서 정의한다. 그 뒤에 사용할 애노테이션 이름이 온다.

public @interface MyAnnotation {
    String name() default "sskim";
    int age() default 30;
}

애노테이션은 엘리먼트(element)를 멤버로 가질 수 있다. 각 엘리먼트는 타입과 이름으로 구성되어 있고, 디폴트 값을 가질 수 있다.

엘리먼트 이름을 name으로 선언하기 default 값을 주었다.

엘리먼트의 타입으로는 int, double, String, 열거 타입, Class 타입 그리고 이들의 배열 타입을 사용할 수 있다.

엘리먼트의 뒤에는 () 처럼 메서드 형식으로 작성해야 한다.

 

실행 클래스는 뭔가 좀 더 봐야 할 듯;;; 어렵네;;

 

애노테이션 프로세서

참조

자바의 정석 - www.yes24.com/Product/Goods/24259565?OzSrank=2

이것이 자바다  - www.yes24.com/Product/Goods/15651484

자바의 신 - www.yes24.com/Product/Goods/42643850

오라클 Docs -  docs.oracle.com/javase/tutorial/java/index.html

 

 


라이브 강의

@Retention

SOURCE -> CLASS -> RUNTIME

SOURCE는 컴파일하고 난 후에는 애노테이션 자체가 없어진다. 대표적으로 @Override

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public @interface Override {
}

@Override는 컴파일 이후에 바이트코드에는 있을 필요가 없기 때문에 SOURCE

 

CLASS는 바이트코드에 들어는 있는데 RUNTIME 이와는 다르게 리플렉션은 안된다. CLASS로 정의해 놓으면  클래스로드에서 읽어서 메모리에 적재하는데 메모리 읽어올 때 애노테이션 정보를 누락시킨다. 그래서 리플렉션이 안된다.

@Documented
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotation {
    String name() default "sskim";
    int age() default 30;
}
public class App {

    public static void main(String[] args) {

        Annotation[] annotations = MyAnnotation.class.getAnnotations();
        for (Annotation annotation : annotations) {
            System.out.println(annotation);
        }
    }
}

//결과
@java.lang.annotation.Documented()
@java.lang.annotation.Retention(value=RUNTIME)

 

@Inherited

@Documented
@Inherited
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotation {

    String name() default "sskim";
    int age() default 30;
}

MyAnnotation 애노테이션을 쓴 MyHello 클래스

@MyAnnotation
public class MyHello {

    private String privateName;

    public String publicName;
}

MyHello 클래스를 상속받은 MyHelloChild 클래스

public class MyHelloChild extends MyHello {
}

getAnnotations()와 getDeclaredAnnotations()의 차이

getAnnotations()는 Inherited 된 애노테이션이 나타나는데 getDeclaredAnnotations는 그 안에 선언되어있는 거만 가져온다. 실제 선언된 애노테이션을 출력하기 때문에 아래의 결과 차이가 있다.

@MyAnnotation
public class App {

    public static void main(String[] args) {

        Annotation[] annotations = MyHelloChild.class.getAnnotations();
        System.out.println("======getAnnotations()=======");
        for (Annotation annotation : annotations) {
            System.out.println(annotation);
        }
        Annotation[] declaredAnnotations = MyHelloChild.class.getDeclaredAnnotations();
        System.out.println("======getDeclaredAnnotations()=======");
        for (Annotation declaredAnnotation : declaredAnnotations) {
            System.out.println(declaredAnnotation);
        }
    }
}

======getAnnotations()=======
@week12.MyAnnotation(name="sskim", age=30)

======getDeclaredAnnotations()=======

Field 도 똑같다.

getFields() 는 private 말고 public 으로 선언된 것만 나온다.

getDgetDeclaredFields()는 클래스에 선언된! 것이 나온다 그래서 private 한 거도 나온다.

만약 MyHelloChild 클래스에 private 으로 선언된 변수가 있었으면 getDgetDeclaredFields() 했을 때도 출력이 된다.

@MyAnnotation
public class App {

    public static void main(String[] args) {

        Field[] fields = MyHelloChild.class.getFields();
        System.out.println("======getFields()=======");
        for (Field field : fields) {
            System.out.println(field);
        }
        Field[] declaredFields = MyHelloChild.class.getDeclaredFields();
        System.out.println("======getDeclaredFields()=======");
        for (Field declaredField : declaredFields) {
            System.out.println(declaredField);
        }
    }
}

//결과
======getFields()=======
public java.lang.String week12.MyHello.publicName
======getDeclaredFields()=======

 

 
728x90
댓글
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
«   2024/05   »
1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31
글 보관함