지네릭스, 열거형, 애너테이션 3

LeeKyoungChang·2022년 3월 6일
0
post-thumbnail

Java의 정석 의 책을 읽고 정리한 내용입니다.

 

📚 3. 애너테이션(annotation)

📖 A. 애너테이션이란?

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

 

💡 참고
애너테이션(annotation)의 뜻은 주석, 주해, 메모이다.

 

✔️ @Test

@Test // 이 메서드가 테스트 대상임을 테스트 프로그램에게 알린다.
public void method() {
	...
}
  • @Test는 이 메서드를 테스트해야 한다는 것을 테스트 프로그램에게 알리는 역할을 할 뿐,
    메서드가 포함된 프로그램 자체에는 아무런 영향을 미치지 않는다.
  • 주석처럼 존재하지 않는 것이나 다름없다.

 

💡 참고
JDK에서 제공하는 애너테이션 java.lang.annotaion 패키지에 포함되어 있다.

 

📖 B. 표준 애너테이션

일부는 메타 애너테이션으로 애너테이션을 정의하는데 사용되는 애너테이션의 애너테이션이다.

애너테이션설명
Override컴파일러에게 오버라이딩하는 메서드라는 것을 알린다.
Deprecated앞으로 사용하지 않을 것을 권장하는 대상에 붙인다.
SuppressWarnings컴파일러의 특정 경고메시지가 나타나지 않게 해준다.
SafeVarargs지네릭스 타입의 가변인자에 사용한다.(JDK1.7)
FunctionalInterface함수형 인터페이스라는 것을 알린다.(JDK1.8)
Nativenative메서드에서 참조되는 상수 앞에 붙인다.(JDK1.8)
Target*애너테이션이 적용가능한 대상을 지정하는데 사용한다.
Documented*애너테이션 정보가 javadoc으로 작성된 문서에 포함되게 한다.
Inherited*애너테잇녀이 자손 클래스에 상속되도록 한다.
Retention*
애너테이션이 유지되는 범위를 지정하는데 사용한다.
Repeatable*애너테이션을 반복해서 적용할 수 있게 한다.(JDK1.8)
  • 자바에서 기본적으로 제공하는 표준 애노테이션
  • *가 붙은 것은 메타 애너테이션

 

✔️@Override

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

 

✔️ Deprecated

앞으로 사용하지 않을 것을 권장하는 대상에 붙인다.

 

✔️ @FunctionalInterface

  • @FunctionalInterface 애너테이션을 붙이면 컴파일러가 '함수형 인페이스'를 올바르게 선언했는지 확인하고, 잘못된 경우 에러를 발생시킨다.
  • 함수형 인터페이스를 선언할 때는 이 애너테이션을 반드시 붙이는 것이 좋다.
    • 함수형 인터페이스는 추상 메서드가 하나뿐이어야 한다는 제약이 있다.

 

✔️ @SuppressWarnings

  • 컴파일러가 보여주는 경고메세지가 나타나지 않게 억제해준다.
  • 묵인해야하는 경고가 발생하는 대상에 반드시 @SuppressWarnings를 붙여서 컴파일 후에 어떤 경고 메세지도 나타나지 않게 해야한다.

주로 사용되는 것 : deprecation, unchecked, rawtypes, varargs

  • deprecation은 앞에서 살펴본 것과 같이 @Deprecated가 붙은 대상을 사용해서 발생하는 경고를 억제할 때 사용한다.
  • unchecked는 지네릭스로 타입을 지정하지 않았을 때 발생하는 경고를 억제할 때 사용한다.
  • rawtypes는 지네릭스를 사용하지 않아서 발생하는 경고를 억제할 때 사용한다.
  • varargs는 가변인자의 타입이 지네릭 타입일 때 발생하는 경고를 억제할 때 사용한다.
@SuppressWarnings("unchecked") // 지네릭스와 관련된 경고를 억제한다.
ArrayList list = new ArrayList(); // 지네릭 타입을 지정하지 않았다.
list.add(obj); // 여기서 경고가 발생한다.
  • 억제하려는 경고 메세지를 애너테이션의 뒤에 괄호()안에 문자열로 지정하면 된다.

 

✔️ @SafeVarags

  • 메서드에 선언된 가변인자의 타입이 non-reifiable타입의 경우 : 해당 메서드를 선언하는 부분과 호출하는 부분에서 unchecked 경고가 발생한다.

  • 해당 코드에 문제가 없다면 이 경고를 억제하기 위해서 @SafeVarargs를 사용해야 한다.

  • @SafeVarags 애너테이션은 static이나 final이 붙은 메서드와 생성자에만 붙일 수 있다.

  • 즉, 오버라이드될 수 있는 메서드에는 사용할 수 없다는 뜻이다.

  • 컴파일 후에도 제거되지 않는 타입을 reifiable타입

  • 제거되는 타입을 non-reifiable타입

  • 지네릭 타입들 대부분은 컴파일 시에 제거되므로 non-reifiable타입이다.

 

💡 참고
reifiablere(다시) + -ify(~화 하다) + -able(~할 수 있는)의 합성어로 직역하면,
다시 ~화 할 수 있는이라는 뜻이다. 컴파일 후에도 타입정보가 유지되면 reifiable타입이다.

 

  • 메서드를 선언할 때 @SafeVarargs대신, @SuppressWarnings("unchecked")로 경고를 억제하려면, 메서드 선언뿐만 아니라 메서드가 호출되는 곳에도 애너테이션을 붙여야 한다.
@SafeVarargs // 'unchecked' 경고를 억제한다.
@SuppressWarnings("varargs") // 'varargs" 경고를 억제한다.
public static <T> List<T> asList(T... a) {
	return new ArrayList<>(a);
}
  • @SafeVarargsunchecked 경고는 억제할 수 있지만, varargs 경고는 억제할 수 없기 때문에 습관적으로 @SafeVarargs@SuppressWarnings("varargs")를 같이 붙인다.
  • 가능하면 이 두 애너테이션을 항상 같이 사용하는 것이 좋다.

 

📖 C. 메타 애너테이션

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

 

💡 참고

  • 메타 애너테이션은 java.lang.annotation 패키지에 포함되어 있다.

 

✔️ @Target

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

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

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

 

  • TYPE은 타입을 선언할 때, 애너테이션을 붙일 수 있다는 뜻이고, TYPE_USE는 해당 타입의 변수를 선언할 때 붙일 수 있다는 뜻이다.
  • FIELD는 기본형, 그리고 TYPE_USE는 참조형에 사용된다는 점에 주의하자!
import static java.lang.annotation.ElementType.*;

@Target({FIELD, TYPE, TYPE_USE}) // 적용대상이 FIELD, TYPE, TYPE_USE
public @interface MyAnnotation {	} // Myannotation을 정의

@MyAnnotation // 적용대상이 TYPE인 경우
class MyClass 
	@MyAnnotation // 적용대싱이 FIELD인 경우
    int i;
    
    @MyAnnotation // 적용대상이 TYPE_USE인 경우
    Myclass mc;
}

 

✔️ @Retention

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

애너테이션 유지정책(retention policy)의 종류

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

 

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public@interface Override{}
  • @Override@SuppressWarnings처럼 컴파일러가 사용하는 애너테이션은 유지 정책이 SOURCE다.
  • 컴파일러를 직접 작성할 것이 아니면, 이 유지정책은 필요없다.

 

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface FunctionalInterface {}
  • 유지 정책을 RUNTIME으로 하면, 실행 시에 리플렉션(reflection)을 통해서 클래스 파일에 저장된 애너테이션의 정보를 읽어서 처리할 수 있다.
  • @FunctionalInterface@Override처럼 컴파일러가 체크해주는 애너테이션이지만, 실행 시에도 사용되므로 유지 정책이 RUNTIME으로 되어 있다.

 

💡 참고
지역 변수에 붙은 애너테이션은 컴파일러만 인식할 수 있으므로, 유지정책이 RUNTIME인 애너테이션을 지역변수에 붙여도 실행 시에는 인식되지 않는다.

 

✔️ @Documented

  • 애너테이션에 대한 정보가 javadoc으로 작성한 문서에 포함되도록 한다.
  • 기본 애너테이션 중에 @Override@SuppressWarnings를 제외하고 모두 이 메타 애너테이션이 붙어있다.
@Documented
@Retentiond(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public@interface FunctionalInterface{}

 

✔️ @Inherited

  • 애너테이션이 자손 클래스에 상속되도록 한다.
  • @Inherited가 붙은 애너테이션을 조상 클래스에 붙이면, 자손 클래스도 이 애너테이션이 붙은 것과 같이 인식된다.
@Inherited // @SupperAnno가 자손까지 영향을 미치게
@Interface SupperAnno {}

@SuperAnno
class Parent {}

class Child extends Parent {} // Child에 애너테이션이 붙은 것으로 인식한다.
  • Child 클래스는 애너테이션이 붙지 않았지만, 조상인 Parent클래스에 붙은 @SuperAnno가 상속되서 Child 클래스에도 @SupperAnno가 붙은 것처럼 인식된다.

 

✔️ @Repeatable

@Repeatable(ToDos.class) // ToDo애너테이션을 여러 번 반복해서 쓸 수 있게 한다.
@interface ToDO {
	String value();
}
  • @ToDo라는 애너테이션이 위 코드처럼 정의되었을 때,
    MyClass클래스에 @ToDo를 여러 번 붙이는 것이 가능하다.
@ToDo("delete test codes.")
@ToDo("override inherited methods")
class MyClass {
	...
}
  • 이 애너테이션들은 하나로 묶어서 다룰 수 있는 애너테이션도 추가로 정의해야 한다.
@interface ToDos { // 여러 개의 ToDo애너테이션을 담을 컨테이너 애너테이션 ToDos
	ToDo[] value(); // ToDo 애너테이션 배열타입의 요소를 선언, 이름이 반드시 value여야 한다.
}

@Repeatable (ToDos.class) // 괄호 안에 컨테이너 애너테이션을 지정해 줘야 한다.
@interface ToDo {
	String value();
}

 

✔️ @Native

  • 네이티브 메서드(native method)에 의해 참조되는 상수 필드(constant field)에 붙이는 애너테이션이다.
@Native public static final long MIN_VALUE = 0x800000000000L;
  • 네이티브 메서드는 JVM이 설치된 OS의 메서드를 말한다.
  • 자바에서는 메서드의 선언부만 정의하고 구현은 하지 않는다.
  • 추상 메서드처럼 선언부만 있고 몸통은 없다.
public class Object {
	private static native void registerNatives(); // 네이티브 메서드
    
    static {
    	registerNatives(); // 네이티브 메서드를 호출한다.
    }
    protected natvie Object clone() throws CloneNotSupportedException;
    public final native Class<?> getClass();
    public final native void notify();
    public final native void notifyAll();
    public final native void wait(long timeout) throws InterruptedException;
    public native int hashCode();
    	...
}
  • 모든 클래스의 조상인 Object클래스의 메서드들은 대부분 네이티브 메서드다.
  • 네이티브 메서드는 실제로 호출되는 것은 OS의 메서드다.

 

📖 D. 애너테이션 타입 정의하기

  • 새로운 애너테이션을 정의하는 방법은 아래와 같다.
  • @기호를 붙이는 것을 제외하면 인터페이스를 정의하는 것과 동일하다.
@interface 애너테이션 이름 {
	타입 요소이름(); // 애너테이션의 요소를 선언한다.
    ...
}
  • @Override'는 애너테이션이고, Override는 애너테이션의 타입이다.

 

✔️ 애너테이션의 요소

  • 애너테이션 내에 선언된 메서드를 애너테이션의 요소(elemnet)라고 한다.

 

💡 참고

  • 애너테이션에도 인터페이스처럼 상수를 정의할 수 있지만, 디폴트 메서드는 정의할 수 없다.

 

  • 애너테이션의 요소는 반환 값이 있고 매개변수는 없는 추상 메서드의 형태를 가지며, 상속을 통해 구현하지 않아도 된다.
  • 다만, 애너테이션을 적용할 때 이 요소들의 값을 빠짐없이 지정해줘야 한다.
  • 요소의 이름도 같이 적어주므로 순서는 상관없다.

 

@interface TestInfo {
	int count() default 1; // 기본값을 1로 지정한다.
}
@TestInfo // @TestInfo(count=1)과 동일
public class NewClass {...}
  • 애너테이션의 각 요소는 기본값을 가질 수 있으며, 기본값이 있는 요소는 애너테이션을 적용할 때 값을 지정하지 않으면 기본값이 사용된다.
  • 기본값으로 null 을 제외한 모든 리터럴이 가능하다.

 

@interface TestInfo {
	String value();
}
@TestInfo("passed") // @TestInfo(value="passed")와 동일하다.
class NewClass {...}
  • 애너테이션 요소가 오직 하나뿐이고 이름이 value인 경우, 애너테이션을 적용할 때 요소의 이름을 생략하고 값만 적어도 된다.

 

@interface TestInfo {
	String[] testTools();
}

@Test(testTools = {"JUnit", "AuthoTester"}) // 값이 여러 개인 경우
@Test(testTools = "JUnit") // 값이 하나일 때는 괄호 {}생략 가능
@Test(testTools = {} // 값이 없을 때는 괄호{}가 반드시 필요하다.
  • 요소의 타입이 배열인 경우, 괄호{}를 사용해서 여러 개의 값을 지정할 수 있다.

 

@interface TestInfo {
	String[] info() default {"aaa", "bbb"}; // 기본값이 여러 개인 경우. 괄호{}사용
    String[] info2() default "ccc"; // 기본값이 하나인 경우, 괄호 생략 가능
}
@TestInfo // @TestInfo(info={"aaa","bbb"}, info2="ccc")와 동일
@TestInfo(info2={}) // @TestInfo(info={"aaa","bbb"}, info2={})와 동일
class NewClass {...}
  • 기본값을 지정할 때도 마찬가지로 괄호{}를 사용할 수 있다.

 

@interface SuppressWarnings {
	String[] value();
}
  • 요소의 타입이 배열일 때도 요소의 이름이 value이면, 요소의 이름을 생략할 수 있다.
  • 예를 들어서, @SuppressWarnings의 경우, 요소의 타입이 String배열이고 이름이 value다.

 

// @SuppressWarnings(value={"deprecation", "unchecked"})
@SuppressWarnings({"derpecaion", "unchecked")
class newClass {...}
  • 그래서 애너테이션을 적용할 때 요소의 이름을 생략할 수 있는 것이다.

 

✔️ java.lang.annotation.Annotation

  • 모든 애너테이션의 조상은 Annotation이다.
  • 애너테이션은 상속이 허용되지 않는다.
package java.lang.annotation;

public interface Annotation { // Annotation자신은 인터페이스다.
	boolean equals(Object obj);
    int hashCode(0;
    String to String();
    
    Class<? extends Annotation> annotationType(); // 애너테이션의 타입을 반환한다.
  • Annotation은 애너테이션이 아니라 일반적인 인터페이스로 정의되어 있다!

 

Class<AnnotaionTest> cls = AnnotationTest.class;
Annotation[] annoArr = AnnotationTest.class.getAnnotations();

for(Annotation a : annoArr) {
	System.out.println("toString():"+a.toString());
    System.out.println("hashCode():"+a.hashCode());
    System.out.println("equals():"+a.equals(a));
    System.out.println("annotationType():"+a.annotationType()):
}
  • 모든 애너테이션 객체에 대해서 equals(), hashCode(), toString()과 같은 메서드를 호출하는 것이 가능하다.

 

✔️ 마커 애너테이션 Marker Annotation

요소가 하나도 정의되지 않은 애너테이션

@Target(ElemnetType.METHOD)
@Retention(RetetionPolicy.SOURCE)
public @interface Override{} // 마커 애너테이션. 정의된 요소가 하나도 없다.

@Target(ElementType.METHOD)
@Retetion(RetentionPolicy.SOURCE)
public @interface Test {} // 마커 애너테이션. 정의된 요소가 하나도 없다.

 

✔️ 애너테이션 요소의 규칙

- 요소의 타입은 기본형, String, enum, 애너테이션, Class만 허용된다.
- ()안에 매개변수를 선언할 수 없다.
- 예외를 선언할 수 없다.
- 요소를 타입 매개변수로 정의할 수 없다.
@interface AnnoTest {
    int id = 100;	// Ok 상수 선언. static final int id = 100;
    String major(int i, int j); // 에러. 매개변수를 선언할 수 없음
    String minor() throws Exception; // 에러. 예외를 선언할 수 없음.
    ArrayList<T> list(); // 에러. 요소의 타입에 타입 매개변수 사용 불가
}

 

import java.lang.annotation.*;

@Deprecated
@SuppressWarnings("1111") // 유효하지 않은 애너테이션은 무시된다.
@TestInfo(testedBy="aaa", testDate=@DateTime(yymmdd="160101", hhmmss="235959"))
class AnnotationsEx5 {
	public static void main(String args[]) {
    	// AnnotationEx5의 Class객체를 얻는다.
        Class<AnnotationEx5> cls = AnnotationEx5. class;
        
        TestInfo anno = (TestInfo)cls.getAnnotation(TestInfo.class);
        System.out.println("anno.testedBy()="+anno.testedBy());
        System.out.println("anno.testDate().yymmdd()="+anno.testDate().yymmdd());
        System.out.println("anoo.testDate().hhmmss()="+anno.testDate().hhmmss());
        
        for(String str : anno.testTools())
        	System.out.println("testTools="+str);
            
        System.out.println();
        // AnnotationEx5에 적용된 모든 애너테이션을 가져온다.
        Annotation[] annoArr = cls. getAnnotations();
        
        for(Annotaion a : annoArr)
        	System.out.println(a);
    } // main의 끝
}

@Retention(RetentionPolicy.RUNTIME) // 실행 시에 사용가능하도록 지정한다.
@interface TestInfo {
    int		count() 	default 1;
    String	testedBy();
    String[]	testTools() 	default "JUnit";
    TestType	testType()	default TestType.FIRST;
    DateTime	testDate();
}

@Retention (RetentionPolicy.RUNTIME) // 실행 시에 사용가능하도록 지정한다.
@interface DateTime {
	String yymmdd();
    String hhmmss();
}

enum TestType {FIRST, FINAL}
anno.testedBy()=aaa
anno.testDate().yymmdd()=160101
anno.testDate().hhmmss()=235959
testTools=JUnit

@java.lang.Deprecated()
@TestInfo(count=1, testType=FIRST, testTools=[JUnit], testedBy=aaa, testDate=@DateTime(yymmdd=160101, hhmmss=235959))

AnnotationEx5 클래스에 적용된 애너테이션을 실행시간에 얻으려면

Class<AnnotationEx5> cls = AnnotationEx5.class;
TestInfo anno = (TestInfo)cls.getAnnotation(TestInfo.class);

와 같이 사용하면 된다.

  • AnnotationEx5.class는 클래스 객체를 의미하는 리터럴이다.

 

TestInfo anno = (TestInfo)cls.getAnnotation(TestInfo.class);
System.out.println("anno.testedBy()="+anno.testedBy());

// AnnotationEx5에 적용된 모든 애너테이션을 가져온다.
Annotation[] annoArr = cls.getAnnotations();
  • 클래스 객체에는 해당 클래스에 대한 모든 정보를 갖고 있는데, 애너테이션의 정보도 포함되어 있다.
  • 클래스 객체가 가지고 있는 getAnnotation()이라는 메서드에 매개변수로 정보를 얻고자하는 애너테이션을 지정해주거나 getAnnotations()로 모든 애너테이션을 배열로 받아올 수 있다.

 

profile
"야, (오류 만났어?) 너두 (해결) 할 수 있어"

0개의 댓글

관련 채용 정보