본문 바로가기
자바/스터디

[자바 스터디] 12주차 과제 - 애노테이션(Annotation)

by jeonghaemin 2021. 2. 5.
728x90

본 게시글은 백기선 님의 live-study 과제를 수행하면서 작성한 글입니다.

목표

자바의 애노테이션에 대해 학습하세요.

학습할 것 (필수)

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

애노테이션이란?

소스 코드 안에 다른 프로그램을 위한 정보를 미리 약속된 형식으로 포함시킨 것.

애노테이션은 주석처럼 프로그래밍 언어에 영향을 미치지 않으면서 다른 프로그램에게 유용한 정보를 제공할 수 있다.

예를 들어 우리는 테스트 메서드를 작성할 때 메서드 위에 @Test 애노테이션을 붙인다.

@Test
void testMethod() {
 //...
}

여기서 @Test 애노테이션은 '이 메서드를 테스트해야 한다.'라는 것을 JUnit과 같은 테스트 프레임워크에게 알리는 역할을 할 뿐, 메서드가 포함된 프로그램 자체에는 주석과 같이 아무런 영향을 끼치지 않는다.

자바에서 제공하는 표준 애노테이션

  • JDK에서 제공하는 표준 애노테이션은 'java.lang.annotaion' 패키지에 포함되어 있다.

@Override

  • 위치 : 메서드
  • 역할 : 조상 메서드를 오버라이딩하는 메서드라는 것을 컴파일러에게 알려준다.
class Parent {

    void parentMethod() {}

}

class Child extends Parent {

    void parentMethodd() { ... }

}

Child 클래스에선 조상 클래스인 Parent 클래스의 parentMethod()를 오버라이딩을 하려 했지만, 메서드명 끝에 d를 하나 더 붙여서 오타가 발생했다. 이 경우 컴파일러는 parentMethodd()를 오버라이딩된 메서드가 아닌 새로운 메서드라고 판단한다.

class Child extends Parent {
    @Override
    void parentMethodd() { ... }

}

하지만 다음과 같이 @Override 애노테이션을 사용하면 컴파일러는 같은 이름의 조상 메서드가 있는지 확인하고, 만약 없다면 아래와 같은 에러 메시지를 출력해준다.

java: method does not override or implement a method from a supertype

@Deprecated

  • 위치 : 필드, 메서드
  • 역할 : 이 애노테이션이 붙은 대상은 다른 것으로 대체되었으니 더 이상 사용하지 않는 것이 좋다고 알려주는 것.

새로운 버전의 JDK가 나올 때, 새로운 기능이 추가되거나 기존의 부족한 부분들을 개선하기도 한다. 이 과정에서 기존에 있던 것들을 대체하는 기능이 나오더라도 호환성을 위해 함부로 삭제 할 수 없다.

그래서 @Deprecated 애노테이션을 통해 다른 것으로 대체되었으니 더 이상 사용하지 않는 것이 좋다고 알려준다.

실제로 @Deprecated 애노테이션이 붙어있는 java.util.getDate() 메서드를 예로 살펴보면 메서드 설명에 Calendar.get() 메서드로 대체되었다고 설명하고 있다.

@FunctionalInterface

  • 위치 : 함수형 인터페이스
  • 역할 : 컴파일러가 함수형 인터페이스를 올바르게 선언했는지 확인하고, 잘못 선언된 경우 에러를 발생시킨다.
@FunctionalInterface
public interface FunctionalInterfaceSample {
    void hello();
}

@SuppressWarnings

  • 역할 : 컴파일러가 보여주는 경고 메시지가 출력되지 않게 해 준다.

경우에 따라 컴파일러가 발생하는 경고를 무시해야 할 때가 있는데, 이 경우에 경고 메시지가 출력되지 않게 해 준다.

@SuppressWarnings 애노테이션이 막아주는 경고 메시지의 종류는 여러 가지가 있는데, 대표적으로 다음과 같다.

  • deprecation : @Deprecated가 붙은 대상의 경고를 무시.
  • unchecked : 지네릭스로 타입을 지정하지 않을 때 발생하는 경고를 무시.
  • rawtypes : 지네릭스를 사용하지 않아서 발생하는 경고를 무시.
  • varargs : 가변 인자의 타입이 지네릭 타입일 때 발생하는 경고를 무시.
//지네릭스 타입을 지정하지 않아 경고 발생
List list = new ArrayList();

자바 컴파일 시에 -Xlint 옵션을 사용하면 경고 메시지를 출력할 수 있다.

App.java:14: warning: [rawtypes] found raw type: ArrayList
        ArrayList list = new ArrayList();

출력된 경고 메시지 중 []에 있는 것이 경고 메시지의 종류이다.

@SuppressWarnings("rawtypes")
List list = new ArrayList();

위와 같이 @SuppressWarnings()의 인자로 경고 메시지 종류를 문자열로 전달하고 다시 컴파일해보면 경고 메시지는 출력되지 않는다.

여러 경고 메시지를 동시에 사용하려면 다음과 같이 문자열 배열을 인자로 전달하면 된다.

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

@SafeVarargs

  • 위치 : static 또는 final이 붙은 메서드나 생성자

  • 역할 : 메서드에 선언된 가변 인자의 타입이 non-refiable타입일 경우, 해당 메서드를 선언하는 부분과 호출하는 부분에서 "unchecked" 경고가 발생하는데 이 경고를 무시하기 위해 사용한다.

    non-refiable 타입 : 지네릭스와 같이 컴파일 이후에 제거되는 타입.

class Hello<T> {
    T[] arr;

    @SafeVarargs
    public Hello(T... arr) {
        this.arr = arr;
    }

    @SafeVarargs
    public static <T> Hello<T> asList(T... a) {
        return new Hello<>(a);
    }
}

메타 애노테이션

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

@Target

애노테이션이 적용 가능한 대상을 지정하는 데 사용되며 적용 대상의 종류는 다음과 같다.

  • ANNOTATIN_TYPE : 애노테이션
  • CONSTRUCTOR : 생성자
  • FIELD : 필드(멤버 변수, enum 상수), 기본형에 사용
  • LOCAL_VARIABLE : 지역변수
  • METHOD : 메서드
  • PACKAGE : 패키지
  • PARAMETER : 매개변수
  • TYPE : 타입(클래스, 인터페이스(애노테이션 포함), enum)을 선언할 때
  • TYPE_PARAMETER : 지네릭의 타입 매개변수(JDK 1.8) (예: class Hello<@MyAnnotation T> { })
  • TYPE_USE : 타입이 사용되는 모든 곳(JDK 1.8), 타입의 변수를 선언할 때

원하는 적용 대상을 @Target 애노테이션의 인자로 전달하면 된다.

import static java.lang.annotation.ElementType.*;

@Target({FIELD, TYPE, METHOD})
@interface MyAnnotation { }

@MyAnnotation //TYPE
class Hello {

    @MyAnnotation //FIELD
    int num;

    @MyAnnotation //METHOD
    public int getNum() {
        return num;
    }
}

@ Retention

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

  • SOURCE : 소스 파일에만 존재, 클래스 파일에는 존재하지 않음.
  • CLASS(디폴트 값) : 클래스 파일에 존재, 실행 시에 사용불가.
  • RUNTIME : 클래스 파일에 존재, 실행시에 사용 가능.

SOURCE

@Override처럼 컴파일러가 사용하는 애노테이션의 유지 정책이기 때문에 컴파일러를 작성할 것이 아니라면 이 유지 정책은 사용할 일이 없다.

RUNTIME

실행 시에 리플렉션을 통해 클래스 파일에 저장된 애노테이션의 정보를 읽어서 처리할 수 있다.

CLASS

클래스 파일에 JVM에 로딩될 때는 애노테이션이 무시되어 실행 시에 정보를 얻을 수 없기 때문에 디폴트 값이지만 잘 사용되지 않는다.

@ Documented

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

@Inherited

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

부모 클래스에 붙은 애노테이션에 @Inherited가 있다면 자식 클래스에도 해당 애노테이션이 붙어 있는 것과 같다.

import static java.lang.annotation.ElementType.*;

@Inherited
@Target(TYPE)
@interface MyAnnotation { }

@MyAnnotation
class Parent { }

//@MyAnnotation이 붙어있는 것으로 인식
class Child extends Parent { }

@Repeatable

하나의 대상에 같은 애노테이션을 여러 번 붙일 수 있도록 해준다.

일반 애노테이션과 달리 하나의 대상에 같은 이름의 애노테이션이 여러 개가 사용되기 때문에 이를 묶어서 보관할 컨테이너 애노테이션을 별도로 사용해야 한다.

//여러 개의 MyAnnotation을 담을 컨테이너 애노테이션
@interface MyAnnotations {
    MyAnnotation[] value(); //이름이 반드시 value여야함
}

@Repeatable(MyAnnotations.class) //인자로 컨테이너 애노테이션 지정
@interface MyAnnotation {
     String value();
}

@MyAnnotation("Hello")
@MyAnnotation("World")
@MyAnnotation("!!")
class Hello { 

}

@Native

네이트브 메서드에 의해 참조되는 상수 필드에 붙이는 애노테이션이다.

애노테이션 정의하는 방법

@interface 애노테이션이름 {
    타입 요소이름();
    ...
}

애노테이션에 선언된 메서드를 요소라고 하며, 반환 값이 있고 매개변수는 없는 추상 메서드의 형태를 가진다.

애노테이션을 사용할 때 기본값이 있는 요소들을 제외한 나머지 요소들은 무조건 값을 전달해줘야 한다.

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

값을 지정하지 않았을 때 사용될 기본값을 지정할 수 있다.

@Target(TYPE)
@interface MyAnnotation {
     int count() default 1; //기본값 지정 1
}

@MyAnnotation(count = 10)
@MyAnnotation() //생략시 기본값인 1

요소의 타입이 배열인 경우, {} 사용하여 여러 개의 값을 지정할 수 있고, 값이 하나인 경우 {}를 생략할 수 있다.

@Target(TYPE)
@interface MyAnnotation {
     String[] strings();
}

@MyAnnotation(strings = {"a", "b", "c"})
@MyAnnotation(strings = "a") //값이 1개 인경우 {} 생략 가능.
@MyAnnotation(strings = {}) //값이 0개인 경우에 {} 생략 불가능.

요소의 개수가 1개이고, 이름이 value인 경우, 요소의 이름을 생략할 수 있다.(배열인 경우에도 사용 가능)

@interface MyAnnotation {
     String value();
}

@MyAnnotation("abcd")

값을 지정할 필요가 없다면, 요소를 하나도 정의하지 않아도 되는데 이를 '마커 애노테이션'이라고 한다.

@Target(TYPE)
@interface MyMarkerAnnotation { }

@MyMarkerAnnotation
class Hello {

}

애노테이션 프로세서

  • javac에 있는 빌드 툴로, 컴파일 시점에 애노테이션에 대해 다양한 처리를 위해 사용된다.
  • 컴파일 시점에 컴파일러가 소스 코드의 애노테이션 정보를 읽어, 새로운 코드를 생성하거나 변경할 수 있고, 리소스 파일의 생성도 가능하다.

애노테이션 프로세서의 예

  • lombok
  • @Override
  • AutoService : java.utilServiceLoader용 파일 생성 유틸리티
  • Dagger2 : 컴파일 타임 DI 제공

장점

  • 컴파일 시점에 조작하기 때문에 런타임 시 비용이 제로이다.

단점

  • 기존의 코드를 고치는 방법은 공개된 API를 제공하지 않는다.

출처

댓글