어노테이션에 대해서

5196 단어 JavaJava

어노테이션이란

컴파일 과정과 실행과정에서 코드를 어떻게 컴파일하고 처리할 것인지 알려주는 정보이다.
프로그램에게 추가적인 정보를 제공해주는 메타 데이터이다. 컴파일러에게 코드 문법 에러를 체크하도록 정보를 제공하고, IDE에서 빌드 시 코드를 자동으로 생성할 수 있도록 정보를 제공하고, 실행 시 특정 기능을 실행하도록 정보를 제공한다.

표준 어노테이션

자바에서 기본적으로 제공하는 어노테이션이다.

  • @SafeVarargs
@SafeVarargs
@SuppressWarnings("varargs")
public static <T> List<T> asList(T...a) {
	return new ArrayList<T>(a);	// ArrayList(E[] array)를 호출, 경고발생
}
위 코드에서 asList()의 매개변수가 가변인자인 동시에 제네릭 타입이다. 
메서드에 선언된 타입 T는 컴파일 과정에서 Object로 바뀌어 Object[]가 된다. 
여기서 Object[]에는 모든 타입의 객체가 들어올 수 있으므로 배열로 ArrayList<T>를 생성하는 것은 위험하다고 경고하는 것이다.
하지만, asList()가 호출되는 부분은 컴파일러가 체크해서 타입 T가 아닌 다른 타입이 들어가지 못하게 할 것이기 때문에
위의 코드는 아무런 문제가 없다. 이럴 때 @SafeVarargs를 붙여서 이 메서드의 가변인자는 타입 안정성이 있다라고 컴파일러에게
알려서 경고가 발생하지 않도록 해야한다.

@SafeVarargs로 unchecked 경고는 억제할 수 있지만, varargs 경고는 억제할 수 없기 때문에 @SafeVarargs와 @SuppressWarnings("varargs")를 같이 붙인다.

메타 어노테이션

어노테이션을 위한 어노테이션으로 어노테이션을 정의할 때 어노테이션의 적용대상이나 유지기간 등을 지정하는데 사용된다.

  • @Target
    적용 가능한 대상을 지정하는데 사용된다. @Target으로 지정할 수 있는 어노테이션 적용 대상의 종류는 다음과 같다.
  • @Retention
    어노테이션이 유지되는 기간을 지정하는데 사용된다. 어노테이션 유지 정책 종류는 다음과 같다.
    1) SOURCE: 소스 파일에만 존재, 클래스 파일에는 존재하지 않음
    2) CLASS: 클래스 파일에 존재, 실행 시에 사용불가, 기본값
    3) RUNTIME: 클래스 파일에 존재, 실행 시에 사용 가능
    컴파일러가 사용하는 유지 정책은 SOURCE이다. 만약, RUNTIME으로 하면 실행 시에 리플렉션을 통해 클래스 파일에 저장된 어노테이션 정보를 읽어서 처리할 수 있다.
  • @Documented
    어노테이션에 대한 정보가 javadoc으로 작성한 문서에 포함되도록 한다.
  • @Inherited
    어노테이션이 하위 클래스에 상속되도록 한다. 이 어노테이션을 상위 클래스에 붙이면 하위 클래스에도 이 어노테이션이 붙은 것과 같이 인식된다.
  • @Repeatable
    어노테이션을 여러 번 붙이고 싶을 때 사용한다.
	@interface ToDos {
		Todo[] value();
	}

	@Repeatable(Todos.class)
	@interface ToDo {
		String value();
	}	
  • @Native
    네이티브 메서드에 의해 참조되는 상수 필드에 붙이는 어노테이션이다.

어노테이션을 구현하는 방법과 고려해야할 점

어노테이션을 직접 정의하기 위해서는 다음과 같이 @ 기호를 붙이는 것을 제외하면 인터페이스를 정의하는 것과 동일하다.

@interface 어노테이션이름 {
	타입 요소이름();	// 어노테이션의 요소를 선언한다.
}
1. 어노테이션의 요소는 반환값이 있고 매개변수는 없는 추상 메서드의 형태를 가지며, 상속을 통해
구현하지 않아도 된다. 다만, 어노테이션을 적용할 때 요소의 값을 빠짐없이 지정해줘야한다.
요소의 이름을 같이 적어주기 때문에 순서는 상관없다.
2. 어노테이션의 요소는 기본값을 가질 수 있으며, 어노테이션을 적용할 때 기본값을 지정하지 않으면 기본값이 사용된다.
3. 어노테이션 요소가 오직 하나뿐이면 어노테이션을 적용할 때 요소의 이름을 생략하고 값만 적어도 된다.
4. 요소의 타입이 배열인 경우 괄호 {}를 사용해서 여러 개의 값을 지정할 수 있다. (기본값도 마찬가지)
5. 모든 어노테이션의 조상은 Annotation이지만, 어노테이션은 상속이 허용되지 않는다.
6. 요소가 하나도 정의되어 있지 않은 어노테이션을 마커 어노테이션이라고 한다.
  • 어노테이션 요소의 규칙
    1) 요소의 타입은 기본형, String, enum, 어노테이션, Class만 허용된다.
    2) 반환값이 있고 매개변수는 없는 추상 메서드의 형태
    3) 예외를 선언할 수 없다.
    4) 요소를 타입 매개변수로 정의할 수 없다. (요소의 타입에 타입 매개변수 사용불가, 제네릭)
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE_USE)
@Repeatable(ChickenContainer.class)
public @interface Chicken {

    String value();
}

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE_USE)
public @interface ChickenContainer {

    // 자기 자신이 감싸고 있을 어노테이션의 배열을 가지고 있어야 한다.
    Chicken[] value();
}

@Chicken("양념")
@Chicken("마늘간장")
public class AnnotationTest {

    public static void main(String[] args) {

        // 방법1: 클래스에서 getDeclaredAnnotationsByType으로 Chicken 타입을 읽어오는 방법
        Chicken[] chickens = AnnotationTest.class.getDeclaredAnnotationsByType(Chicken.class);
        Arrays.stream(chickens).forEach(c -> {
            System.out.println(c.value());
        });

        // 방법2: getAnnotation으로 ChickenContainer로 가져오는 방법
        ChickenContainer chickenContainer = AnnotationTest.class.getAnnotation(ChickenContainer.class);
        Arrays.stream(chickenContainer.value()).forEach(c -> {
            System.out.println(c.value());
        });
    }
}

마커 인터페이스와 마커 어노테이션

마커 인터페이스는 아무 메서드도 담지 않고, 단지 자신을 구현하는 클래스가 특정 속성을 가짐을 표현해주는 인터페이스이다. (ex) Serializable, Cloneable)
마커 어노테이션은 멤버를 포함하지 않으며 데이터로 구성되지 않는다. 단지 어노테이션 선언을 표시하여 정의한 이유를 표시하는 것이다. (ex) @Override)

새로 추가하는 메서드 없이 단지 타입 정의가 목적이라면 마커 인터페이스를 사용하고, 클래스나 인터페이스 외의 프로그램 요소(모듈, 패키지, 필드, 지역변수 등)에 마킹해야 하거나 어노테이션을 적극적으로 활용하는 프레임워크의 일부로 그 마커를 편입시키고자 한다면 마커 어노테이션이 올바른 선택이다.
(ex) 마커 인터페이스 활용 예시 - 직렬화는 Serializable 마커 인터페이스를 보고 대상이 직렬화할 수 있는 타입인지 확인한다. 즉, 마커 인터페이스는 이를 구현한 클래스의 인스턴스들을 구분하는 타입으로 쓸 수 있다.)

*참고 자료
Java의 정석
Effective Java

좋은 웹페이지 즐겨찾기