[JAVA] 애너테이션 ( Annotation ) ②

🏃‍♂️ 들어가기 앞서..

본 게시물은 스터디 활동 중에 작성한 게시물로 자바의 정석-기초편 교재를 학습하여 정리하는 글입니다.
※ 스터디 Page : 〔투 비 마스터 : 자바〕

*해당 교재의 목차 순서와 구성을 참고하여 작성하며
각 내용마다 부족할 수 있는 내용이나 개인적으로 궁금한 점은
추가적인 검색을 통해 채워나갈 예정입니다.



🧶 애너테이션 정의

형식은 기존의 인터페이스과 동일하고

차이점은 @을 사용하느냐에 있다.

@interface 애너테이션 이름 {
	타입 요소이름() ; // 요소 선언
    ...
    ..
}
/*
일반적인 interface의 모습
interface 인터페이스 {
	타입 요소이름();
    ...
    ..
}
*/
  • 애너테이션 명 : 애너테이션 " 타입 "으로서 정의되고 → @애너테이션 : 애너테이션으로 사용되는 것

이렇게 애너테이션 내에 메서드가 선언되는데

애너테이션의 메서드는 추상 메서드이고

이를 " 애너테이션의 요소(Element) "라고 한다.
(단, 인터페이스처럼 상수는 정의할 수 있을지라도 디폴트 메서드는 정의할 수 없다.)

enum TestType { FIRST, FINAL }

/* 내부 선언 메서드(요소)들은 여러가지 타입들로 정의될 수 있음 */
@interface Test {
	int count(); // 정수
    String testedBy() ; // 문자열
    String[] testTools() ; // 문자열 배열
    TestType testType(); // Enum
	DateTime testDate(); // 다른 애너테이션 
}

@interface DateTime {
	String yymmdd() ;
    String hhmmss() ;
}

위처럼
요소들을 다양한 타입으로 정의할 수 있는 것을 알 수 있다.

이렇게 생성한 애너테이션을 사용하려면
아래와 같이 사용하면 된다.
( 순서는 상관 없음. )

@Test(
	count = 3,
    testedBy = "Jung",
    testTools = {"JUnit", "AutoTester"},
    testType = TestType.FIRST 
    testDate = @DateTime(yymmdd="", hhmmss="")
)
// 호출 방식은 " 애너테이션.요소() " 로 값을 호출한다.
// Test.count() → 3

@Test
public class NewTest {
	... // Test 애너테이션 사용가능
}

요소의 타입이 배열일 경우에는
중괄호 {}를 사용하면 된다.

/*
요소의 타입이 배열이면
앞서 여러가지 값을 다룰 때 사용했던 것처럼
`{}`을  사용해서 지정하면 된다.
*/

@interface Test {
	String[] Tools();
}

@Test(Tools = {"JUnit", "AutoTester"})
//@Test(Tools = "JUnit") 하나도 가능 - "{}" 생략 가능

/* ★ 값이 없을 땐 "{}"를 생략할 수 없다 */
// @Test(Tools = {})
public class NewTest {...}

단, 모든 요소의 값을 지정해주어야 한다는 것을 주의해야한다.



만약 한 가지 요소는 매번 동일한 값을 사용할 때,
매번 작성해주는 것은 비효율적이다.

그런 경우,
기본값 (default)도 지정해 줄 수 있다.
( null을 제외하고 모든 리터럴 가능 )

@interface Test {
	int count() default 3;
    String testedBy() default "Jung" ;
	String[] testTools() default {"JUnit", "AutoTester"};    
}


@Test // 이렇게 기본값이 모두 있는 경우엔 애너테이션만 달아줘도 되고
@Test(testTools ={}) // 이렇게 원하는 것만 골라서 값을 지정해줘도 된다.
public class NewTest {...}

또한
이전 게시물에서도 알아봤던 value의 경우인데
" 애너테이션의 요소가 단 하나뿐이고 이름 : value 인 경우 "는
애너테이션 적용 시,
요소 이름을 생략하고 값만 적어도 된다.

@interface Test {
	String value();
}

@Test("passed")
public class NewTest {...}

이 value가 " 배열인 경우 " 에도 동일하다.

//@SuppressWarnings 의 경우
@interface SuppressWarnings {
	String[] value();
}

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

※ TIPS

  • class에 대해 클래스이름.class ;클래스 객체를 만들 수 있다.
    애너테이션에 대한 정보도 담겨 있다! ( 유효하지 않은 애너테이션은 무시 )
@Test
class Test {
	Class<Test> cls = Test.class ; // cls라는 Test 클래스 타입 매개변수 생성 
}
  • 클래스 객체에서 getAnnotation( 특정 애너테이션 ) / getAnnotations() 메서드 등으로 애너테이션 정보만 가져올 수 있다.
@SuppressWarnings("1111") // 유효하지 않아서 getAnnotations 메서드 사용시 무시한다.
@Deprecated
@Test(
	count = 3,
    testedBy = "Jung",
    testTools = {"JUnit", "AutoTester"},
    testType = TestType.FIRST 
)
class Test {
	Class<Test> cls = Test.class ;  
    Test anno = cls.getAnnotation(Test.class) ; // 위와 같이 .class를 통해 애너테이션 클래스 객체를 생성
    System.out.println(anno.testedBy()); // Jung 출력
    System.out.println(anno.testType()); // FIRST 출력

    Annotation[] annoArr = cls.getAnnotations() ; // 모든 애너테이션을 배열로 가져온다.

    for( Annotation a : annoArr) {
    	System.out.println(a) ;
    }
    /*
    @java.lan.Deprecated()
    @Test(count=3, testedBy=Jung, testTools = [JUnit, AutoTester], testType=FIRST) 
    */
}

애너테이션의 조상?

사실 모든 애너테이션에게는 조상이 있다.
바로 java.lang.annotation패키지에 정의되어 있는
Annotation이다.

그래서 모든 애너테이션의 조상이기는 하나

애너테이션의 특성 상
" 상속이 불가능 "하기 때문에
조상으로 지정할 수 없다.

/* 에러 발생!! 불가능!! */
@interface Test extends Annotation {
	...
}

앗 그런데 잠깐!

지금까지 애너테이션을 살펴봤을 때,
가장 많이 보인 키워드가 있다.

바로
" interface "

그럼 implements는 안되는가?

심지어 implements도 불가능하다.

이 애너테이션에 extendsimplements 를 시도했다가는
이런 메세지와 함께 실패를 맛보게 된다.

Annotation type declaration cannot have explicit superinterfaces
Annotation type declaration cannot have an explicit superclass

" 그럼 도대체 진짜 interface는 뭐가 있는가 ?! "

그렇다.
java.lang.annotation패키지에 정의되어 있는
Annotation만 일반적인 인터페이스이다.
심지어는 애너테이션도 아니다..

그냥 조상일 뿐인 본인(?)만 인터페이스인 것이다...

실제 Annotation인터페이스는 아래와 같이 구성되어 있다.

package java.lang.annotation;

public interface Annotation {
	/* 추상 메서드 */
    boolean equals( Object obj );
    int hashCode() ;
    String toString();
    
    Class<? extends Annotation> annotationType(); // 애너테이션의 타입을 반환한다.
}

하지만 조심해야할 것
상속이 불가능하다고 자손이 아닌 것이 아니고
모든 애너테이션은 Annotation의 자손이기 때문에

위에 보이는
equals / hashCode / toString 과 같은 메서드들을
여간 인터페이스들과 다를 바 없이

모든 애너테이션 객체에서 호출하는 것이 가능 하다.



📍 마커 애너테이션

" 값을 지정할 필요가 없는 경우 "
요소가 하나도 정의되지 않은 애너테이션

Serializable이나 Cloneable 인터페이스 처럼
요소가 하나도 정의되지 않은 애너테이션을 가르킨다.
( 애너테이션을 달 때, 아무 값도 입력하지 않음 )

보통
값이 필요없더라도
프로그램 내부에 " 애너테이션이 달려있는 코드에 대한 정보를 전달하는 역할 "로 쓰인다.



💢 " 요소 "의 규칙

1. 요소의 타입은 【 기본형 | String | enum | 애너테이션 | 클래스(Class) 】만 허용

* Class라 함은 '설계도 객체'를 뜻한다. (java.lang 패키지와 유용한 클래스 게시글 참고)

2. 추상메서드 괄호 안매개변수를 선언할 수 없다.

3. 예외선언할 수 없다.

4. 요소를 타입 매개변수정의할 수 없다.

*지네릭 타입 등으로도 사용할 수 없다.

@interface AnnoTest {
	ArrayList<T> list() ; // 타입 매개변수로 요소가 쓰일 수 없다.
}

좋은 웹페이지 즐겨찾기