ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [whiteship] 온라인스터디 - 12주차. 애노테이션
    IT 발자취.../JAVA 2021. 2. 6. 22:35

    github.com/whiteship/live-study/issues/12

     

    12주차 과제: 애노테이션 · Issue #12 · whiteship/live-study

    목표 자바의 애노테이션에 대해 학습하세요. 학습할 것 (필수) 애노테이션 정의하는 방법 @retention @target @documented 애노테이션 프로세서 마감일시 2021년 2월 6일 토요일 오후 1시까지.

    github.com

    목표

    자바의 애노테이션에 대해 학습하세요.

    학습할 것 (필수)

    애노테이션

    어노테이션은 주석이라는 뜻을 가지고 있다.

    기본적으로 우리가 아는 주석은 // 또는 /**/ 이렇게 생겼는데, 어노테이션과 일반적인 주석은 뭐가 다를까?

    - 어노테이션의 역할도 주석과 크게 다르지 않는다.

    - 일반 주석과 큰 차이점은 코드를 작성할 수 있다는 것이 다르다.

    - 코드를 작성할 수 있다는 뜻은 어노테이션으로 뭔가를 할 수 있다는 뜻이 된다.

    - 어노테이션도 enum과 마찬가지로 1.5에 등장했다.

     

    애노테이션을 정의하는 방법

    public @interface Make {}

    애노테이션의 조상을 확인한다. 바이트 코드로 확인해 본다.

     

    $ javac Make.java
    $ javap -c Make.class
    Compiled from "Make.java"
    public interface com.gintire.pure.week12.Make extends java.lang.annotation.Annotation {
    }
    

     

    확인해보면 java.lang.annotation.Annotation이 조상인 것을 확인할 수 있다.

    애노테이션은 인터페이스로 구성되어있기 때문에 굳이 구현할려면 implements를 하던가 익명클래스로 만들어야한다.

    Annotation annotation = new Annotation() {
            @Override
            public Class<? extends Annotation> annotationType() {
                return null;
            }
        }

    애노테이션은 리플렉션 기술을 이용해서 사용된다는 것을 짐작할 수 있다.

     

    애노테이션 요소의 규칙

     - 요소의 타입은 기본형, String, enum, 애노테이션, Class만 허용된다.
     - ()안에 매개변수는 선언할 수 없다.
     - 예외를 선언할 수는 없다.
     - 요소를 타입 매개변수로 정의 할 수 없다.

    표준 애노테이션

    자바에서 제공하는 애노테이션은 크게 2가지로 구성되어있다.

    하나는 자바 코드를 작성할 때 사용되는 애노테이션이고, 다른 하나는 애노테이션을 정의하기 위해 필요한 것들이다.

     

    @Override

    오버라이드를 할 때 사용되며, 메소드가 오버라이드 되었는지 알려주는 역할을 한다.

    오버라이드는 아래와 같이 생겼다.

    @Target(ElementType.METHOD)
    @Retention(RetentionPolicy.SOURCE)
    public @interface Override {
    }

    @Deprecated

    사용되지 않을 대상에 붙여진다.

    애노테이션이 존재하는 이유는 버전이 올라가면서, 더 이상 사용이 되지 않는 것들이 존재한다.

    예를 들어, Date클래스를 확인해 본다.

    /**
         * Allocates a {@code Date} object and initializes it so that
         * it represents midnight, local time, at the beginning of the day
         * specified by the {@code year}, {@code month}, and
         * {@code date} arguments.
         *
         * @param   year    the year minus 1900.
         * @param   month   the month between 0-11.
         * @param   date    the day of the month between 1-31.
         * @see     java.util.Calendar
         * @deprecated As of JDK version 1.1,
         * replaced by {@code Calendar.set(year + 1900, month, date)}
         * or {@code GregorianCalendar(year + 1900, month, date)}.
         */
        @Deprecated
        public Date(int year, int month, int date) {
            this(year, month, date, 0, 0, 0);
        }

    Date 클래스를 보면 생성자가 deprecated가 존재한다는 것을 확인할 수 있다.

    주석을 읽어보면 Calendar 클래스를 사용하도록 권장한다.

     

    이는 이전 버전과의 호환성 때문에 삭제 처리하지 않고 남겨두고 해당 애노테이션을 사용한다.

     

    애너테이션 코드를 확인해본다.

    @Documented
    @Retention(RetentionPolicy.RUNTIME)
    @Target(value={CONSTRUCTOR, FIELD, LOCAL_VARIABLE, METHOD, PACKAGE, MODULE, PARAMETER, TYPE})
    public @interface Deprecated {
        /**
         * Returns the version in which the annotated element became deprecated.
         * The version string is in the same format and namespace as the value of
         * the {@code @since} javadoc tag. The default value is the empty
         * string.
         *
         * @return the version string
         * @since 9
         */
        String since() default "";
    
        /**
         * Indicates whether the annotated element is subject to removal in a
         * future version. The default value is {@code false}.
         *
         * @return whether the element is subject to removal
         * @since 9
         */
        boolean forRemoval() default false;
    }

    애너테이션 코드를 확인하면 구현부는 없다고 확인할 수 있다.

    - 애너테이션은 인터페이스로 되어있기 때문에 구현이 되지 않는 것을 알 수 있다.

    - default, static은 작성되지 않는다.

     

    @FunctionalInterface

    이 애노테이션은 이 인터페이스는 무조건 함수형으로 사용한다는 뜻이다.

    함수형으로 작성할 때는 몇가지 규칙이 존재하는데, 그 중에 인터페이스는 단 하나의 메소드만 존재해야한다.

     

    @Documented
    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.TYPE)
    public @interface FunctionalInterface {}

    @SuppressWarnings

    컴파일러가 보여주는 경고 메시지가 보이지 않게 억제 해준다고 한다.

    @Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE, MODULE})
    @Retention(RetentionPolicy.SOURCE)
    public @interface SuppressWarnings {
        /**
         * The set of warnings that are to be suppressed by the compiler in the
         * annotated element.  Duplicate names are permitted.  The second and
         * successive occurrences of a name are ignored.  The presence of
         * unrecognized warning names is <i>not</i> an error: Compilers must
         * ignore any warning names they do not recognize.  They are, however,
         * free to emit a warning if an annotation contains an unrecognized
         * warning name.
         *
         * <p> The string {@code "unchecked"} is used to suppress
         * unchecked warnings. Compiler vendors should document the
         * additional warning names they support in conjunction with this
         * annotation type. They are encouraged to cooperate to ensure
         * that the same names work across multiple compilers.
         * @return the set of warnings to be suppressed
         */
        String[] value();
    }

     

     

     

    @Target

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

    위에서 Override를 다시 확인해본다.

    @Target(ElementType.METHOD)
    @Retention(RetentionPolicy.SOURCE)
    public @interface Override {
    }

    메소드에서 사용되어진다고 한다. 즉, 다른 필드 변수라던지, 생성자, 파라미터 등에서는 사용하다.

     

    메소드말고, 타입, 필드, 애노테이션 타입, 생성자, 지역 변수, 모듈, 패키지, 파라미터, 파입 파라미터, 타입 유즈에 사용할 수 있다고 한다.

     

     

    @Documented
    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.ANNOTATION_TYPE)
    public @interface Target {
        /**
         * Returns an array of the kinds of elements an annotation type
         * can be applied to.
         * @return an array of the kinds of elements an annotation type
         * can be applied to
         */
        ElementType[] value();
    }

    @Retention

    애노테이션이 유지되는 범위를 지정하는데 사용된다.

    Override를 다시 확인해 본다.

    @Target(ElementType.METHOD)
    @Retention(RetentionPolicy.SOURCE)
    public @interface Override {
    }

    확인결과 런타임까지 유지가 된다고 한다.

    종류는 다음과 같다.

    @documented

    애노테이션 프로세서

     

     

    리뷰

     

    애너테이션은 주석이다.

     

    런타임 중에 알아내야하는 값은 들어갈 수 없다.

    컴파일러 수준에서 해석되고 완전히 정적이어야 한다.

    - 문서화도 해야하므로 정적이어야 한다 ( javadoc )

     

    Retention

    - 언제까지 유지할 것인가.

    - 기본값 : CLASS

    - 반드시 값을 입력해줘야한다.

     

    - SOURCE : 소스코드에만 유지하겠다. 컴파일하고 나면 어노테이션이 사라진다. ( == 주석으로만 쓰겠다. 바이트코드에 남아있지 않다. ) 

      - 컴파일 할 때만 사용하겠다.  ( 눈으로 보는용으로 )

      - 예 > Override => Override 해야하는데 안했을 때, 컴파일러는 먼저 확인하고 알림을 줌 and 개발자가 확인

    - CLASS : 어노테이션에 대한 정보를 바이트 코드에도 남겨두겠다. 

    바이트코드를 확인하면 애노테이션을 확인할 수 있다.

    클래스 정보를 클래스 로더가 불러올 때 Retention이 CLASS면 누락 시킨다.

     

    - RUNTIME : 

    클래스파일에 남겨져 있다면, 리플렉션이 가능해진다.

    클래스 정보에 들어가 있기 때문에, 런타임 중에 클래스 정보를 확인할 수 있다.

    클래스로더가 불러오면서 

     

    바이트 코드로 불러오는게 빠르냐, 리플렉션으로 불러오는게 빠르냐?? 정답은 모름

    리플렉션은 부하가 있다.

     

    리플랙션 비용 : 클래스 로더가 읽어오는 부하, 메모리에 적재하는 비용, 

    바이트코드 비용 : 바이트 코드의 크기에 따라 다름

     

    어노테이션이 런타임까지 사용될 것인지, 사용되는 레벨 확인 필요

     

     

    내가 사용하는 애너테이션에 Inherited가 없다면, .getSuperclass()를 사용하면된다.

    public class Main {
        public static void main(String[] args) {
            Annotation[] annotations = ChildTest.class
                    .getSuperclass()
                    .getAnnotations();
            for (int i = 0; i < annotations.length; i++) {
                Annotation annotation = annotations[i];
                System.out.println(annotation.toString());
            }
        }
    }

     

    .getDeclaredAnnotations()를 사용하면, inherited 애너테이션을 무시한다. (== 실제 해당 클래스에 선언된 애너테이션만 가져온다.)

     

    .getField() : public 필드만 가져옴

    .getDeclaredField() : 모두 가져옴

    public class Main {
        public static void main(String[] args) {
            Field[] fields = Test.class
                    .getDeclaredFields();
            for (Field field : fields) {
                System.out.println(field.toString());
            }
        }
    }

    getter / setter 가지고 싸우지말자 - 필드 가져오려면 다 가져올 수 있다.

     

    좋은 질문 : 상속관계에서 자식 클래스에서 getField / getDeclaredField 일 때 결과

    getField는 외부에서 볼 수 있는 값만 가져온다. 하지만 getDeclaredField하면 다 가져올 수 있다.

     

    애너테이션 프로세스 - 일반 개발자는 거의 만들일이 없다.

    알면 좋다. 

     

     

    서비스 로더 >

    인터페이는 내가 만들어도 구현체는 다양한 곳에서 구현된다.

    내가 지정하지 않고 jar만 바꿔서 사용할 수 있다.

    jar 파일에 들어있는 구현체를 가져오는 것

    구현체를 모르고 구현체를 사용하는 방법

    META-INF/services 폴더에 jar

    ===> spring boot에서 spring.factories

     

     

    애너테이션에서 쓰는 value를 쓰면은 어떻게 되는지 알아야한다.

    댓글

Designed by Gintire