『자바의 신 3판』 을 읽고 내용 정리 및 공부한 내용을 정리한 글입니다.
서적: 자바의 신 3판 구입처
어노테이션은 JDK5부터 등장했으며, 클래스나 메소드 등의 선언 시에 @를 사용하는 것을 말한다. 영어로는 Annotation이며, 메타데이터-Metadata-라고 불리기도 한다.
어노테이션은 다음과 같은 상황일 때 사용한다.
클래스, 메소드, 변수 등 모든 요소에 선언할 수 있다. 프로그램에 영향이 있는 어노테이션도 있고 그렇지 않은 것도 있다.
자바 언어에는 사용하기 위해서 정해져 있는 어노테이션이 3개가 있고, 어노테이션을 선언하기 위한 메타 어노테이션이라는 것은 4개가 있다.
물론 Java 버전이 올라가면서 이 어노테이션의 개수도 자연스레 증가하게 될 것이다. 하지만, JDK6까지는 단지 3개의 어노테이션이 있다고 생각하자.
해당 메소드가 부모 클래스에 있는 메소드를 Override 했다는 것을 명시적으로 선언한다.
어떤 메소드가 Override 되었는지, 혹은 Override가 잘못된 메소드가 있는지 등을 명확하게 컴파일러에게 알려주기 위해 지정해주는 것이라고 생각하면 된다.
public class Parent {
public void printName() {
System.out.println("printName() - Parent");
}
}
public class AnnotationOverride extends Parent{
@Override
public void printName() {
System.out.println("AnnotationOverride");
}
@Override
public void printName(String args) { // compile error
System.out.println("AnnotationOverride ");
}
}
11장에서 배웠다시피 미리 만들어져 있는 클래스나 메소드가 더 이상 사용되지 않는 경우가 있다.
그런 것을 deprecated라고 하며, 컴파일러에게 더 이상 사용하지 않으니 경고를 띄워달라고 일러 주는 것이라고 생각하면 된다.
사용되지 않는 클래스나 메소드를 그냥 지워버리게 되면, 해당 클래스나 메소드를 참조하는 다른 개발자가 만든 프로그램이 변경된 사항을 모르고 있다면 컴파일 시 에러가 발생할 것이다.
그러므로, 하위 호환성을 위해서 해당 어노테이션으로 선언하는 것은 꼭 필요하다.
가장 좋은 방법은 계도 기간을 거쳐 알림을 준 이후에 지우는 것이 바람직하다.
public class AnnotationDeprecated {
@Deprecated
public void noMoreUse() {
}
}
public class AnnotationSample {
public void useDeprecated() {
AnnotationDeprecated child=new AnnotationDeprecated();
child.noMoreUse(); // 컴파일 시 warning 출력
}
}
간혹 코딩을 하다 보면 컴파일러에서 경고(warning)을 알리는 경우가 있다. 그럴 때 컴파일러에게 “일부러 이렇게 코딩한 것이니 경고할 필요 없다”고 알려주는 것이다.
하지만, 이 어노테이션을 남용할 경우 Deprecated된 메소드를 사용해도 모르고 넘어갈 수 있으니 유의해야 한다.
다음은 deprecated된 메소드를 호출할 때 사용한 예제이다.
public class AnnotationSample {
@SuppressWarnings("deprecation")
public void useDeprecated() {
AnnotationDeprecated child=new AnnotationDeprecated();
child.noMoreUse();
}
}
소괄호 속에 문자열을 넘겨준다. 이처럼 어노테이션의 종류에 따라서 속성값을 지정하는 것도 존재한다.
메타 어노테이션(Meta anotation)이라는 것은 어노테이션을 우리가 선언할 때 사용한다.
어노테이션을 어떤 것에 적용할지를 선언할 때 사용한다.
@Target(ElementType.METHOD)
이처럼 괄호 안에 적용 대상을 지정하는데, 그 대상 목록은 다음과 같다.
요소 타입 | 대상 |
---|---|
CONSTRUCTOR | 생성자 선언 시 |
FILED | enum 상수를 포함한 필드(field) 값 선언 시 |
LOCAL_VARIABLE | 지역 변수 선언 시 |
METHOD | 메소드 선언 시 |
PACKAGE | 패키지 선언 시 |
PARAMETER | 매개 변수 선언 시 |
TYPE | 클래스, 인터페이스, enum 등 선언 시 |
얼마나 오래 어노테이션 정보가 유지되는지를 선언한다.
@Retention(RetentionPolicy.RUNTIME)
괄호 안에 지정하는 적용 가능한 대상은 다음과 같다.
요소 타입 | 대상 |
---|---|
SOURCE | 어노테이션 정보가 컴파일 시 사라짐 |
CLASS | 클래스 파일에 있는 어노테이션 정보가 컴파일러에 의해 참조 가능함. 하지만, 가상 머신(virtual Machine)에서는 사라짐 |
RUNTIME | 실행 시 어노테이션 정보가 가상 머신에 의해서 참조 가능 |
해당 “어노테이션에 대한 정보가 Javadocs(API) 문서에 포함된다는 것”을 선언한다.
모든 자식 클래스에서 부모 클래스의 어노테이션을 사용 가능하다는 것을 선언한다.
@interface은 어노테이션을 선언할 때 사용한다. 다음 코드와 함께 살펴보자.
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(ElementType.METHOD) // 1)
@Retention(RetentionPolicy.RUNTIME) // 2)
public @interface UserAnnotation { // 3)
public int number(); // 4)
public String text() default "This is first annotation"; // 5)
}
이렇게 만든 어노테이션은 다음과 같이 메소드를 선언할 때 사용할 수 있다.
public class UserAnnotationSample {
@UserAnnotation(number=0)
public static void main(String args[]) {
UserAnnotationSample sample=new UserAnnotationSample();
}
@UserAnnotation(number=1)
public void annotationSample1(){
...
}
@UserAnnotation(number=2,text="second")
public void annotationSample2(){
...
}
@UserAnnotation(number=3,text="third")
public void annotationSample3(){
...
}
}
어노테이션 선언 클래스에 지정해 놓은 각 메소드의 이름에 해당하는 값을 소괄호 안에 넣어주어야만 한다.
즉, number()와 text()에 해당하는 어노테이션 값들을 지정해 주면 된다. 추가로 text()의 경우 default를 사용하여 기본 값을 지정해 주었기 때문에 별도로 값을 지정해 주지 않아도 컴파일 하는 데 전혀 문제가 없지만, number()는 기본값이 지정되어 있지 않으므로 반드시 값을 정해주어야만 한다.
만약, 생성자나 클래스에서도 사용할 수 있도록 하려면 다음과 같이 선언한다.
@Target({ElementType.METHOD, ElementType.TYPE})
두 개 이상의 어노테이션을 선언할 때에는 중괄호를 한 후 쉼표(,)로 구분해 주면 된다.
이렇게 직접 만든 어노테이션은 리플렉션(reflection) API를 사용하여 어노테이션에 대한 정보를 확인할 수 있다.
리플렉션 API란 자바의 리플렉션이라는 API에서 제공하는 클래스들이다. 크게 아래 두 가지가 있다.
import java.lang.reflect.Method;
public class UserAnnotationCheck {
public static void main(String args[]) {
UserAnnotationCheck sample=new UserAnnotationCheck();
sample.checkAnnotations(UserAnnotationSample.class);
}
public void checkAnnotations(Class useClass) {
Method[] methods=useClass.getDeclaredMethods();
for(Method tempMethod:methods) {
UserAnnotation annotation=tempMethod.getAnnotation(UserAnnotation.class);
if(annotation!=null) {
int number=annotation.number();
String text=annotation.text();
System.out.println(tempMethod.getName() +"() : number="+number+" text="+text);
} else {
System.out.println(tempMethod.getName() +"() : annotation is null.");
}
}
}
}
어노테이션은 용도에 따라서 다음과 같이 나눌 수 있다.
이러한 어노테이션이 만들어지기 전까지는 모든 애플리케이션의 설정을 XML이나 properties라는 파일에 지정해 왔다. 그러면서 설정이 복잡해지고, 어떤 설정이 어디에 쓰이는지 이해하려면 많은 시간이 소요되었다.
그런데, 이런 어노테이션이 만들어 지면서, 각 설정이 필요한 위치에 관련 설정이 존재하면서 코드에 대한 가독성이 매우 좋아졌다.
물론 XML과 같은 설정파일들은 필요한 부분이 존재하기 때문에 아직 남아있다.
롬복 홈페이지: https://projectlombok.org/
롬복(lombok)은 개발자가 필요한 작업을 어노테이션 선언만으로도 편하게 처리할 수 있도록 도와준다.
롬복을 사용하려면 관련 라이브러리가 필요한데, 그 사용법은 위 홈페이지에 있는 설명을 통해서 확인할 수 있다.
private 변수가 있다고 할 때, 그 변수에 접근하기 위해서는 setter()와 getter()가 필요하다. 롬복을 사용하면 다음과 같은 간단한 선언만으로 두 메소드를 생성해준다.
@Getter @Setter private boolean employed = true;
물론 어노테이션이 변환된 모습은 컴파일 단계에서 생성되기 때문에, 역컴파일을 하지 않는 이상 직접 확인할 수 없다.
이외에도 롬복을 제대로 사용하려면 많은 어노테이션과 옵션들이 존재하니 나중에 필요할 때 확인하기 바란다.
또한 단순하게 어노테이션을 사용하는 방법만 알면 실력은 늘지 않는다. 그러니 어노테이션을 지정하면 코드가 내부적으로 어떻게 변환되는지에 대해서 살펴보는 습관을 가지는 것이 좋다.
Me: Override했다는 것을 명시적으로 알려주기 위해서 사용한다.
Me: 경고가 떴을 때, "알고 사용한 것이다"를 컴파일러에게 알려주는 것이다.
Me: 사용하지 않는 메소드 위에 선언하여, 사용자들에게 알려준다.
Me: 메타 어노테이션
Me: java.lang.annotation
Me: 어노테이션을 사용할 범위를 지정한다.
Me: 어노테이션 정보가 얼마나 오래 유지되는지 실행 범위를 지정한다.
Me: 자식 클래스가 부모 클래스의 어노테이션을 상속 가능하다는 의미이다.
Me: @interface