들어가기 전

『자바의 신 3판』 을 읽고 내용 정리 및 공부한 내용을 정리한 글입니다.
서적: 자바의 신 3판 구입처

내용 정리

어노테이션이란?

어노테이션은 JDK5부터 등장했으며, 클래스나 메소드 등의 선언 시에 @를 사용하는 것을 말한다. 영어로는 Annotation이며, 메타데이터-Metadata-라고 불리기도 한다.

어노테이션은 다음과 같은 상황일 때 사용한다.

  • 컴파일러에게 정보를 알려줄 때
  • 컴파일할 때와 설치(deployment) 시의 작업을 지정할 때
  • 실행할 때 별도의 처리가 필요할 때

클래스, 메소드, 변수 등 모든 요소에 선언할 수 있다. 프로그램에 영향이 있는 어노테이션도 있고 그렇지 않은 것도 있다.

어노테이션의 종류

자바 언어에는 사용하기 위해서 정해져 있는 어노테이션이 3개가 있고, 어노테이션을 선언하기 위한 메타 어노테이션이라는 것은 4개가 있다.

사용하기 위해 정해져 있는 어노테이션

  • @override
  • @Deprecated
  • @SupressWarnings

물론 Java 버전이 올라가면서 이 어노테이션의 개수도 자연스레 증가하게 될 것이다. 하지만, JDK6까지는 단지 3개의 어노테이션이 있다고 생각하자.

어노테이션을 선언하기 위한 메타 어노테이션

  • @Target
  • @Retention
  • @Documented
  • @Inherited

정해진 어노테이션

@override

해당 메소드가 부모 클래스에 있는 메소드를 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 ");
    }
}

@Deprecated

11장에서 배웠다시피 미리 만들어져 있는 클래스나 메소드가 더 이상 사용되지 않는 경우가 있다.

그런 것을 deprecated라고 하며, 컴파일러에게 더 이상 사용하지 않으니 경고를 띄워달라고 일러 주는 것이라고 생각하면 된다.

사용되지 않는 클래스나 메소드를 그냥 지워버리게 되면, 해당 클래스나 메소드를 참조하는 다른 개발자가 만든 프로그램이 변경된 사항을 모르고 있다면 컴파일 시 에러가 발생할 것이다.

그러므로, 하위 호환성을 위해서 해당 어노테이션으로 선언하는 것은 꼭 필요하다.

가장 좋은 방법은 계도 기간을 거쳐 알림을 준 이후에 지우는 것이 바람직하다.

public class AnnotationDeprecated {
    @Deprecated
    public void noMoreUse() {
    }
}
public class AnnotationSample {
    public void useDeprecated() {
        AnnotationDeprecated child=new AnnotationDeprecated();
        child.noMoreUse();   // 컴파일 시 warning 출력
    }
}

@SupressWarnings

간혹 코딩을 하다 보면 컴파일러에서 경고(warning)을 알리는 경우가 있다. 그럴 때 컴파일러에게 “일부러 이렇게 코딩한 것이니 경고할 필요 없다”고 알려주는 것이다.

하지만, 이 어노테이션을 남용할 경우 Deprecated된 메소드를 사용해도 모르고 넘어갈 수 있으니 유의해야 한다.

다음은 deprecated된 메소드를 호출할 때 사용한 예제이다.

public class AnnotationSample {
    @SuppressWarnings("deprecation")
    public void useDeprecated() {
        AnnotationDeprecated child=new AnnotationDeprecated();
        child.noMoreUse();
    }
}

소괄호 속에 문자열을 넘겨준다. 이처럼 어노테이션의 종류에 따라서 속성값을 지정하는 것도 존재한다.

메타 어노테이션

메타 어노테이션(Meta anotation)이라는 것은 어노테이션을 우리가 선언할 때 사용한다.

@Target

어노테이션을 어떤 것에 적용할지를 선언할 때 사용한다.

@Target(ElementType.METHOD)

이처럼 괄호 안에 적용 대상을 지정하는데, 그 대상 목록은 다음과 같다.

요소 타입대상
CONSTRUCTOR생성자 선언 시
FILEDenum 상수를 포함한 필드(field) 값 선언 시
LOCAL_VARIABLE지역 변수 선언 시
METHOD메소드 선언 시
PACKAGE패키지 선언 시
PARAMETER매개 변수 선언 시
TYPE클래스, 인터페이스, enum 등 선언 시

@Retention

얼마나 오래 어노테이션 정보가 유지되는지를 선언한다.

@Retention(RetentionPolicy.RUNTIME)

괄호 안에 지정하는 적용 가능한 대상은 다음과 같다.

요소 타입대상
SOURCE어노테이션 정보가 컴파일 시 사라짐
CLASS클래스 파일에 있는 어노테이션 정보가 컴파일러에 의해 참조 가능함.
하지만, 가상 머신(virtual Machine)에서는 사라짐
RUNTIME실행 시 어노테이션 정보가 가상 머신에 의해서 참조 가능

@Documented

해당 “어노테이션에 대한 정보가 Javadocs(API) 문서에 포함된다는 것”을 선언한다.

@Inherited

모든 자식 클래스에서 부모 클래스의 어노테이션을 사용 가능하다는 것을 선언한다.

어노테이션 선언

@interface로 어노터에션 선언

@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)
}
  1. @Target으로 어노테이션 사용 대상을 메소드에 사용할 수 있다고 지정했다.
  2. @Retention으로 실행 시에 이 어노테이션을 참조하게 선언했다.
  3. UserAnnotation 앞에 @interface를 선언하면 @UserAnnotation으로 어노테이션이 사용 가능하다.
  4. 메소드처럼 어노테이션 안에 선언해 놓으면, 이 어노테이션을 사용할 때 해당 항목에 대한 타입으로 값을 지정 가능하다.
  5. default라는 예약어를 사용할 경우에는, 이 예약어 뒤에 있는 값이 이 어노테이션을 사용할 때의 기본값이 된다.
    즉, 값을 지정하지 않아도 default 값으로 지정된다.

사용

이렇게 만든 어노테이션은 다음과 같이 메소드를 선언할 때 사용할 수 있다.

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에서 제공하는 클래스들이다. 크게 아래 두 가지가 있다.

  • Class : 클래스의 정보를 확인할 수 있는 클래스.
    • getDeclaredMethods() 메소드를 호출하면, 해당 클래스에 선언되어 있는 메소드들의 목록을 배열로 리턴한다.
  • Method : 메소드의 정보를 확인할 수 있는 클래스
    • getAnnotation() 이라는 메소드를 호출하면, 해당 메소드에 선언되어 있는 매개 변수로 넘겨준 어노테이션이 있는지 확인하고, 있을 경우 그 어노테이션의 객체를 리턴해준다.
  • 어노테이션에 선언된 메소드를 호출하면, 그 값을 리턴해준다.

예제

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.");
            }
        }
    }
}

어노테이션의 상속

어노테이션의 용도와 사용 이유

어노테이션은 용도에 따라서 다음과 같이 나눌 수 있다.

  • 제약사항 등을 선언하기 위해: @Deprecated, @Override, @NotNull
  • 용도를 나타내기 위해: @Entity, @TestCase, @WebService
  • 행위를 나타내기 위해: @Statefull, @Transaction
  • 처리를 나타내기 위해: @Column, @XmlElement

이러한 어노테이션이 만들어지기 전까지는 모든 애플리케이션의 설정을 XML이나 properties라는 파일에 지정해 왔다. 그러면서 설정이 복잡해지고, 어떤 설정이 어디에 쓰이는지 이해하려면 많은 시간이 소요되었다.

그런데, 이런 어노테이션이 만들어 지면서, 각 설정이 필요한 위치에 관련 설정이 존재하면서 코드에 대한 가독성이 매우 좋아졌다.

물론 XML과 같은 설정파일들은 필요한 부분이 존재하기 때문에 아직 남아있다.

lombok

롬복 홈페이지: https://projectlombok.org/

롬복(lombok)은 개발자가 필요한 작업을 어노테이션 선언만으로도 편하게 처리할 수 있도록 도와준다.

롬복을 사용하려면 관련 라이브러리가 필요한데, 그 사용법은 위 홈페이지에 있는 설명을 통해서 확인할 수 있다.

private 변수가 있다고 할 때, 그 변수에 접근하기 위해서는 setter()와 getter()가 필요하다. 롬복을 사용하면 다음과 같은 간단한 선언만으로 두 메소드를 생성해준다.

@Getter @Setter private boolean employed = true;

물론 어노테이션이 변환된 모습은 컴파일 단계에서 생성되기 때문에, 역컴파일을 하지 않는 이상 직접 확인할 수 없다.

이외에도 롬복을 제대로 사용하려면 많은 어노테이션과 옵션들이 존재하니 나중에 필요할 때 확인하기 바란다.

또한 단순하게 어노테이션을 사용하는 방법만 알면 실력은 늘지 않는다. 그러니 어노테이션을 지정하면 코드가 내부적으로 어떻게 변환되는지에 대해서 살펴보는 습관을 가지는 것이 좋다.

정리해 봅시다.

Q. @Override 어노테이션의 용도는 무엇인가요?

Me: Override했다는 것을 명시적으로 알려주기 위해서 사용한다.

Q. @SupressWarnings 어노테이션의 용도는 무엇인가요?

Me: 경고가 떴을 때, "알고 사용한 것이다"를 컴파일러에게 알려주는 것이다.

Q. @Deprecated 어노테이션의 용도는 무엇인가요?

Me: 사용하지 않는 메소드 위에 선언하여, 사용자들에게 알려준다.

Q. 어노테이션을 선언할 때 사용하는 어노테이션을 무엇이라고 부르나요?

Me: 메타 어노테이션

Q. 4번 문제의 답에 있는 어노테이션들을 사용할 때 import 해야 하는 패키지는 무엇인가요?

Me: java.lang.annotation

Q. @Target 어노테이션의 용도는 무엇인가요?

Me: 어노테이션을 사용할 범위를 지정한다.

Q. @Retention 어노테이션의 용도는 무엇인가요?

Me: 어노테이션 정보가 얼마나 오래 유지되는지 실행 범위를 지정한다.

Q. @Inherited 어노테이션의 용도는 무엇인가요?

Me: 자식 클래스가 부모 클래스의 어노테이션을 상속 가능하다는 의미이다.

Q. 어노테이션을 선언할 때에는 class 대신 어떤 예약어를 사용해야 하나요?

Me: @interface

profile
책을 읽거나 강의를 들으며 공부한 내용을 정리합니다. 가끔 개발하는데 있었던 이슈도 올립니다.

0개의 댓글

Powered by GraphCDN, the GraphQL CDN