순간을 성실히, 화려함보단 꾸준함을

12주차 과제: 애노테이션 본문

백기선님과 함께 하는 자바 스터디

12주차 과제: 애노테이션

폭발토끼 2021. 7. 12. 23:02

목표

자바 에노테이션에 관해 학습하세요.

학습할 것(필수)

  1. 애노테이션 정의하는 방법
  2. @retention
  3. @target
  4. @documented
  5. 에노테이션 프로세서

애노테이션 정의하는 방법

애노테이션이란?

- 자바를 개발한 사람들은 소스코드에 대한 문서를 따로 만들기보단 소스크도와 문서를 하나의 파일로 관리하는 것이 더 효율적이라고 생각했다. 그래서 소스코드의 추석에 소스코드에 관한 정보를 저장하고, 소스코드의 주석으로부터 HTML 문서를 생성해내는 프로그램(javadoc.exe)를 만들어서 사용했다.

import java.lang.annotation.*;

/**
 * Indicates that a method declaration is intended to override a
 * method declaration in a supertype. If a method is annotated with
 * this annotation type compilers are required to generate an error
 * message unless at least one of the following conditions hold:
 *
 * <ul><li>
 * The method does override or implement a method declared in a
 * supertype.
 * </li><li>
 * The method has a signature that is override-equivalent to that of
 * any public method declared in {@linkplain Object}.
 * </li></ul>
 *
 * @author  Peter von der Ah&eacute;
 * @author  Joshua Bloch
 * @jls 8.4.8 Inheritance, Overriding, and Hiding
 * @jls 9.4.1 Inheritance and Overriding
 * @jls 9.6.4.4 @Override
 * @since 1.5
 */
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public @interface Override {
}

위의 소스는 @Override 애노테이션의 소스코드 일부이다.
이러한 기능을 응용하여, 프로그램의 소스코드 안에 다른 프로그램을 위한 정보를 미리 약속된 형식으로 포함시킨 것이 바로 애노테이션이다. 에노테이션은 주석(comment)처럼 프로그래밍 언어에 영향을 미치지 않으면서도 다른 프로그램에게 유용한 정보를 제공할 수 있다는 장점이 있다.

예를 들어 자신이 작성한 소스코드 중에서 특정 메소드만 테스트하기를 원한다면, @Test 라는 애노테이션을 메소드 앞에 붙여 '이 메소드를 테스트해야 한다' 라는 것을 알리는 역할을 한다.

애노테이션 정의하기

@interface 애노테이션이름{
    타입 요소이름{};    //애노테이션의 요소를 선언한다
    ...
}

새로운 애노테이션을 정의하는 방법은 @기호를 붙이는 것을 제외하면 인터페이스를 정의하는 것과 동일하다.

애노테이션에 선언된 메소드를 애노테이션의 요소(element) 라고 한다.

@interface TestInfo{
    int count();
    String testedBy();
    String[] testTools();
    TestType testType();
    Datetime testDate();
}

위의 선언된 @TestInfo 애노테이션은 5개의 요소를 갖는다.
애노테이션의 요소는 반환값이 존재하고, 매개변수는 없는 추상 메소드의 형태를 가지며, 상속을 통해 구현하지 않아도 된다.
단, 애노테이션을 적용할 때 이 요소들의 값을 빠짐없이 지정해주어야 한다. 순서는 상관없다.

애노테이션의 각 요소는 기본값을 가질 수 있고, 기본값이 있는 요소는 애노테이션을 적용할 때 값을 지정하지 않으면 기본값이 된다.

@interface TestInfo{
    int count() default 1;    //기본값을 1로 지정
}
@TestInfo
public class NewClass{ ... }

만약 애노테이션의 요소가 단 한개뿐이고, 그 메소드의 이름이 value인 경우는 애노테이션을 적용할 때 요소의 이름을 생략하고 값만 적어도 된다.중요한 개념이다

@interface TestInfo{
    String value();
}
@TestInfo("password")    //@TestInfo(value="password") 와 동일한 코드이다
class NewClass{ ... }

요소의 타입이 [] 배열인 경우, 괄호{}를 사용하여 여러 개의 값을 지정할 수 있다.

@interface TestInfo{
    String[] testTools();
}
@Test(testTools = {"Junit","AutoTester"})    //값이 여러 개인 경우
@Test(testTools = "Junit")                    //값이 하나일 때는 괄호 {} 생략가능
@Test(testTools = {})                        //값이 없을때는 괄호{} 는 필수적임

요소의 타입이 배열인 경우에도 이름이 value이면, 요소의 이름을 생략할 수 있다.
대표적인 예로 @SuppressWarnings의 경우에는, 요소의 타입이 String이고 이름이 value이다.

@interface SuppressWarnings{
    String[] value();
}

이 때문에 에노테이션을 적용할 때 요소의 이름을 생략할 수 있는 것이다.

//@SuppressWarnings(value={"deprecation","unchecked"})
@SuppressWarnings({"deprecation","unchecked"})
class NewClass{ ... }

애노테이션 요소의 규칙

- 요소의 타입은 기본형, String, enum, 애노테이션, Class만 해당된다.
- ()안에 매개변수를 선언할 수 없다.
- 예외를 선언할 수 없다.
- 요소를 타입 매개변수로 정의할 수 없다.

표준 애노테이션

자바에서 기본적으로 제공하는 애노테이션들은 몇개밖에 존재하지 않는다.

@Override

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

class Parent{
    void parentMethod(){...}
}
class Child extends Parent{
    void parentmethod() {...}         //오버라이딩 하려고 하였으나 오타 발생
}

위의 소스처럼 부모 클래스르 상속받아 Overriding 하려고 하였지만, 오타가 발생해 의도한 바에 어긋나는 경우가 생길 수 있다. 이를 방지하기 위해 @Override 애노테이션을 사용할 수 있다.

class Parent{
    void parentMethod(){...}
}
class Child extends Parent{
    @Override
    void parentmethod() {...}     //Compile Error 발생
}

@Deprecated

새로운 버전의 JDK 가 소개될 때, 새로운 기능이 추가될 뿐만 아니라 기존의 부족했던 기능들을 개선하기도 한다. 이 과정에서 기존의 기능을 대체할 것들이 생겨나도, 이미 여러곳에서 사용되고 있는 것들을 함부로 삭제해버릴 수는 없다.

이러한 이유 때문에 더 이상 사용되지 않는 필드나 메소드에 @Deprecated 애노테이션을 붙이는 것이다. 이 애노테이션이 붙은 대상은 다른 것으로 대처가 되었으니 더 이상 사용하지 않기를 권한다는 것 이다.


    /**
     * Returns the day of the month represented by this {@code Date} object.
     * The value returned is between {@code 1} and {@code 31}
     * representing the day of the month that contains or begins with the
     * instant in time represented by this {@code Date} object, as
     * interpreted in the local time zone.
     *
     * @return  the day of the month represented by this date.
     * @see     java.util.Calendar
     * @deprecated As of JDK version 1.1,
     * replaced by {@code Calendar.get(Calendar.DAY_OF_MONTH)}.
     */
    @Deprecated
    public int getDate() {
        return normalize().getDayOfMonth();
    }

java.util.Date 클래스에 들어가 있는 getDate() 메소드 이다. 그러나 이 메소드는 Deprecated 되었다.

@FunctionalInterface

함수형 인터페이스(functional interface) 를 선언할 때, 이 애노테이션을 붙이면 컴파일러가 '함수형 인터페이스'를 올바르게 선언했는지 확인하고, 잘못된 경우 에러를 발생시킨다. 필수는 아니지만, 붙이면 실수를 방지할 수 있으므로 '함수형 인터페이스'를 선언할 때는 이 애노테이션을 반드시 붙이도록 하자.

함수형 인터페이스(functional interface)란?

함수형 인터페이스란 1개의 추상 메소드를 갖는 인터페이스를 뜻한다. 여러개의 디폴트 메소드가 존재해도 단 한개의 추상 메소드가 존재한다면 함수형 인터페이스 입니다.

@FunctionalInterface
public interface Runnable{
    public abstract void run();    //추상 메소드
}

@SuppressWarnings

컴파일러가 보여주는 경고메세지가 나타나지 않게 억제해준다. 컴파일러의 경고메세지를 묵인하고 넘어가야 할 경우가 생기는 데 이때 @SuppressWarnings 애노테이션을 붙여서 컴파일 후에 경고 메세지가 나타나지 않게 해야한다.
주로 사용되는 것은 deprecation, unchecked, rawtypes, varargs 정도가 된다. JDK버전이 올라가면서 계속 추가될 것이다.

deprecation@Deprecated 가 붙은 대상을 사용해서 발생하는 경고를
unchecked는 제네릭스 타입을 지정하지 않았을 때 발생하는 경고를
rawtypes는 제네릭스를 사용하지 않아서 발생하는 경고를
varargs는 가변인자의 타입이 제네릭 타입일 때 발생하는 경고를 억제할 때 사용한다.

public static void main(String[] args) {
        int a=3;
        ArrayList list = new ArrayList();    //제네릭 타입을 지정하지 않았음
        list.add(a);        //여기서 경고가 발생한다. 
    }
경고메세지 : Unchecked call to 'add(E)' as a member of raw type 'java.util.ArrayList'

@SuppressWarnings 애노테이션을 사용해보자

@SuppressWarnings("unchecked")
    public static void main(String[] args) {
        int a=3;        
        ArrayList list = new ArrayList();
        list.add(a);        //더 이상 경고가 발생하지 않는다.
    }

만약 둘 이상의 경고를 동시에 억제하려면 {} 를 추가로 사용하면 된다.

@SuppressWarnings({"deprecation","unchecked","varargs"})

@SafeVarargs

메소드에 선언된 가변인자의 타입이 non-reifialbe 타입인 경우, 해당 메소드를 선언하는 부분과 호출하는 부분에서 "unchecked" 경고메세지가 발생한다. 해당 코드에 문제가 발생하지 않는다면 이 경고를 억제해야한다.

이 애노테이션은 staticfinal 키워드가 붙은 메소드와 생성자에만 사용할 수 있다. 즉, 오버라이드 될 수 있는 메소드에는 사용할 수 없다는 뜻이다.

reifiable 타입 : 컴파일 이후에 제거되는 타입들을 뜻한다.
non-reifiable 타입 : 컴파일 이후에 제거되지 않는 타입을 뜻한다.

public static <T> List<T> asList(T... a){
    return new ArrayList<T>(a);    //ArrayList(E[] array) 를 호출. 경고발생
}

asList()의 매개변수가 가변인자인 동시에 제네릭 타입이다. 메소드에 선언된 타입 T는 컴파일 과정에서 Object로 바뀐다.
즉, Object[] 가 되는 것이다. Object[] 에는 모든 타입의 객체가 들어있을 수 있으므로, 이 배열로 ArryList를 생성하는 것은 위험하다고 경고하는 것이다. 그러나 asList()가 호출되는 부분을 컴파일러가 체크해서 타입 T가 아닌 다른 타입이 들어가지 못하게 할 것이므로 위의 코드는 아무런 문제가 발생하지 않는다.

이때 @SafeVarargs 애노테이션을 사용하여 경고를 억제하는 것이다. @SafeVarargs 애노테이션을 붙이면 이 메소드를 호출하는 곳에서 발생하는 경고도 억제된다. 그러나 @SuppressWarnings("unchecked") 로 경고를 억제하려면, 메소드 선언뿐만 아니라 메소드가 호출되는 곳에도 애노테이션을 붙여야 한다.

@SafeVarargs 애노테이션은 "unchecked" 경고는 억제할 수 있지만, "varargs" 경고는 억제 할 수 없기 때문에 습관적으로 @SafeVarargs 와 @SuppressWarnings("varargs") 를 같이 붙인다.

-Xlint Option 이란?

모든 권장 경고를 활성화 해준다. 이 릴리스에서는 사용 가능한 모든 경고를 활성화하는 것이 좋다.
-Xlint
Enables all recommended warnings. In this release, enabling all available warnings is recommended.

[-Xlint
Enables all recommended warnings. In this release, enabling all available warnings is recommended.]

$ javac -Xlint com/example/Main.java
com\example\Main.java:26: warning: [varargs] Varargs method could cause heap pollution from non-reifiable varargs parameter a
        return new MyArrayList<>(a);

-Xlint 옵션으로 컴파일 하면 'Varargs' 경고가 발생한 것을 알 수 있다. @SuppressWarnings("varargs")를 추가하면 경고를 제거할 수 있다.

메타 애노테이션

메타 애노테이션이란?

- 에노테이션을 위한 애노테이션이라는 뜻이다. 즉, 애노테이션에 붙이는 애노테이션으로 애노테이션을 정의할 때 애노테이션의 적용대상(target) 이나 유지기간(retention)등을 지정하는데 사용된다. 보통 커스텀 애노테이션을 만들때 사용된다.

@Target

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

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

위의 소스는 @SuppressWarnings 를 정의한 것인데 이 애노테이션에 적용할 수 있는 대상을 @Target 으로 지정한 것이다.
@Target으로 지정할 수 있는 애노테이션 종류는 아래와 같다

ANNOTATION_TYPE : 애노테이션
CONSTRUCTOR : 생성자
FIELD : 필드(맴버변수,enum 상수)
LOCAL_VARIABLE : 지역변수
METHOD : 메소드
PACKAGE : 패키지
PARAMETER : 매개변수
TYPE : 타입(클래스, 인터페이스, enum)
TYPE_PARAMETER : 타입 매개변수(JDK1.8)
TYPE_USE : 타입이 사용되는 모든 곳(JDK1.8)

import java.lang.annotation.ElementType; 
import java.lang.annotation.Target; 

@Target({ElementType.METHOD}) 
public @interface MyAnnotation { 
    String value(); 
}

위와 같이 만들면 메소드에서만 사용이 가능한 애노테이션을 정의할 수 있다.
FIELD는 기본형에 , TYPE_USE는 참조형에 사용된다는 점에 주의하자

@Retention

애노테이션이 유지(retention)되는 기간을 지정하는데 사용된다. 애노테이션의 유지 정책(retention policy)의 종류는 다음과 같다

SOURCE : 소스파일에만 존재, 클래스파일에는 존재하지 않음
CLASS : 클래스 파일에 존재. 실행시에 사용불가. 기본값
RUNTIME : 클래스 파일에 존재. 실행시에 사용가능

@Override 나 @SuppressWarnings 처럼 컴파일러가 사용하는 애노테이션은 유지 정책이 SOURCE 이다. 컴파일러를 직접 작성할 것이 아니면, 이 유지정책은 필요가 없다

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

유지정책을 RUNTIME으로 변경하면 실행 시에 클래스 파일에 저장된 애노테이션의 정보를 읽어서 처리할 수 있다.

CLASS 유지정책은 컴파일러가 애노테이션의 정보를 클래스 파일에 저장할 수 있게는 하지만, 클래스 파일이 JVM에 로딩될 때는 애노테이션의 정보가 무시되어 실행 시에 애노테이션에 대한 정보를 읽을 수 없다.
이것이 CLASS 유지정책의 기본값임에도 불구하고 잘 사용되지 않는 이유이다.

@Documented

애노테이션에 대한 정보가 javadoc으로 작성한 문서에 포함되도록 한다. 자바에서 제공하는 기본 애노테이션 중에 @Override@SuppressWarnings를 제외하고는 모두 이 메타 애노테이션이 붙어있다.

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

애노테이션 프로세서

애노테이션 프로세스란?

- 자바 컴파일러 플러그인의 일종으로 애노테이션에 대한 코드베이스를 검사,수정,생성하는 역할을 수행하는 플러그인을 말한다.
애노테이션 프로세서가 존재하지 않는다면, 그냥 주석에 불가한 개념일 뿐이다.

생성되는 순서

  1. 애노테이션 클래스를 생성한다.
  2. 애노테이션 파서 클래스를 생성한다.
  3. 애노테이션을 사용한다.
  4. 컴파일하면, 애노테이션 파서가 애노테이션을 처리한다.
  5. 자동 생성된 클래스가 빌드 폴더에 추가된다.

Reference

[https://velog.io/@ljs0429777/12%EC%A3%BC%EC%B0%A8-%EA%B3%BC%EC%A0%9C-%EC%95%A0%EB%85%B8%ED%85%8C%EC%9D%B4%EC%85%98]
자바의 정석(남궁민)

'백기선님과 함께 하는 자바 스터디' 카테고리의 다른 글

13주차 과제: I/O  (0) 2021.07.21
[리뷰]12주차 : 애노테이션  (0) 2021.07.15
[리뷰] 11주차 : Enum  (0) 2021.07.10
11주차 과제: Enum  (0) 2021.07.10
[리뷰] 10주차 : Critical Path  (0) 2021.07.03