https://dev-dx2d2y-log.tistory.com/168
[Java] 자바의 정석 독서 #30 - 열거형
https://dev-dx2d2y-log.tistory.com/158 [Java] 자바의 정석 독서 #29 - 제너릭 메서드, 제너릭 형변환https://dev-dx2d2y-log.tistory.com/157 [Java] 자바의 정석 독서 #28 - 제너릭 와일드카드, 공변성어제 교보만 세 곳을
dev-dx2d2y-log.tistory.com
저번에는 열거형에 대해서 다뤘고, 이번에는 어노테이션에 대해서 다뤄보려한다.
Javadoc
JDK1.0부터 소스코드의 주석으로 HTML을 사용하는 Javadoc(javadoc.exe)이 있었다. 지금도 널리 사용되고 있고.
/**
* The common interface extended by all annotation interfaces. Note that an
* interface that manually extends this one does <i>not</i> define
* an annotation interface. Also note that this interface does not itself
* define an annotation interface.
*
* More information about annotation interfaces can be found in section
* {@jls 9.6} of <cite>The Java Language Specification</cite>.
*
* The {@link java.lang.reflect.AnnotatedElement} interface discusses
* compatibility concerns when evolving an annotation interface from being
* non-repeatable to being repeatable.
*
* @author Josh Bloch
* @since 1.5
*/
public interface Annotation { ... }
Annotation 클래스에 적용된 javadoc 내용.
중간중간 '@' 태그들을 확인할 수 있는데 이 태그를 통해 주석 안에 정보를 저장하고, javadoc이 이 정보를 읽어서 문서를 작성하는데 사용된다. 이렇게 @ 태그를 사용하여 무언가를 알리거나, 정보를 알리는 용도로 사용된 것을 소스코드 내에서도 사용하기 시작한 것이 어노테이션이다.
/**
* 컨트롤러
*/
@RestController
public class SendMessageToAIController { ... }
대표적으로 많이 사용되는 어노테이션인 스프링에서의 어노테이션 RestConroller.
위 어노테이션은 이 클래스가 RestController에 해당한다는 것과 스프링 컨테이너에 들어가야한다는 것만을 알려주고 프로그램 자체에는 어떠한 영향도 끼치지 않는다.
@Test
void method() { ... }
클래스 뿐 아니라 메서드에도 적용시켜 "이 메서드가 테스트용도로 사용됩니다."라는 것도 알릴 수 있다.
즉, @ 문자를 사용해 마치 주석처럼 프로그램에 아무 영향도 미치지 않고 단지 어떠한 목적이나 정보를 나타내기 위해 사용하는 것이 어노테이션이다.
표준 어노테이션
자바에서 기본적으로 제공하는 어노테이션들을 표준 어노테이션이라 칭한다.
@Override
오버라이드하고 있는 메서드 앞에서만 붙일 수 있다. 오버라이드된 메서드라는 것을 컴파일러에게 알려주는 역할을하며, 메서드 시그니처를 다르게 입력하는 등 제대로 상속되지 않으면 컴파일에러를 발생시킨다. 즉, 상속과정에서의 실수를 바로잡아주는 역할을 한다.
이펙티브자바에서도 이를 사용하라고 권장했다. 참고하도록.
@Deprecated
기존의 기능을 대체할 새 기능이 생겨도, 이미 기존 기능을 사용하고 있는 코드가 있을 수도 있다. 따라서 대체된 코드를 삭제하지 않고 더 이상 사용되지 않는 필드 또는 메서드에 @Deprecated를 붙여 사용하지 말 것을 권장한다. 물론 '권장'이기 때문에 사용하거나 말거나 개발자 자유긴한데 사용안하는것이 좋다.
@FunctionalInterface
람다를 사용할 때 함수형 인터페이스가 필요한데, 함수형 인터페이스는 몇 가지 제약이 필요하다. @FunctionalInterface 어노테이션은 이 인터페이스가 함수형 인터페이스라는 것과 컴파일과정에서 이 제약을 잘 지켰는지 확인하도록한다.
@SuppressWarnings
컴파일러가 보내는 경고메시지를 나타나지 않게 해준다. 대표적으로 위의 @Deprecated 어노테이션이 붙은 필드나 메서드를 사용하면 컴파일러가 경고메시지를 보내는데, 이 어노테이션을 사용하면 보여주지 않는다. 이외에도 unchecked(제너릭 타입 매개변수 미설정), rawtypes(제너릭 미사용), varargs(가변인자(개수가 변하는 매개변수)의 타입이 제너릭일 때)
@SuppressWarnings("deprecation")
위와같이 특정 경고를 무시할 수 있다. 여러 개 추가하고 싶다면 중괄호를 ( { ... } ) 로 지정해야한다.
@SafeVarargs
제너릭은 컴파일과정에서 타입이 소거되는데, 이러한 특성을 non-reifiable이라고한다. reifiable 타입에 대해서는 제너릭을 사용하지 않는 일반적인 클래스(제너릭이 붙지 않는 List, Comparator도 reifiable에 해당함)에 해당한다.
SafeVarargs 어노테이션의 기본 목적은 "가변인자가 non-reifiable하지만 타입안전하다'라는 것을 알려주기 위해서이다.
메서드에 가변인자를 넘겨주어야할 때 가변인자들이 non-refiable 타입인 경우, unchecked에러가 발생한다.
@SafeVarargs
@SuppressWarnings("varargs")
public static <T> List<T> asList(T... a) {
return new ArrayList<>(a);
}
Arrays에 정의된 asList() 메서드. 여기서 T는 제너릭(non-refiable)이자 가변인자다.
https://dev-dx2d2y-log.tistory.com/158
[Java] 자바의 정석 독서 #29 - 제너릭 메서드, 제너릭 형변환
https://dev-dx2d2y-log.tistory.com/157 [Java] 자바의 정석 독서 #28 - 제너릭 와일드카드, 공변성어제 교보만 세 곳을 돌아다니면서 몇 가지 읽을만한 책을 봤다. 물론 거기에는 CS책도 포함되어있기도 했고.
dev-dx2d2y-log.tistory.com

제너릭에 대해서 다룰 때 컴파일과정에서 타입이 제거된다고했다.
위에서 asList 메서드는 타입이 T (T extends Object)가 되므로 Object 타입으로 컴파일과정에서 바뀌게되고, ArrayList<Object>(a)가 된다. 그러면 ArrayList<>는 내부적으로 Object 배열을 만들게되므로 모든타입에 객체가 들어갈 수 있다. 그러면 제너릭을 사용하는 이유와, ArrayList<>의 한 타입만 받는 특성이 무너지므로 에러를 발생시키게된다.
하지만 asList에서 이미 제너릭을 사용하고있고, asList를 통해 만들어진 ArrayList<>는 값을 추가할 수 없으므로 결과적으로는 타입안전하다. 따라서 그 경고를 무시하기 위해 @SafeVarargs 어노테이션을 붙였다.
다만 @SafeVarargs는 "unchecked" 경고를 표시되지 않게할 수 있지만, varargs는 표시되지 않게 할 수 없기 때문에 보통은 @SuppressWarning 어노테이션을 같이 사용하는 편이다.
메타 어노테이션
메타 어노테이션은 어노테이션 내부에서 적용대상, 유지기간 등 어노테이션의 설정을 위해서 사용하는 어노테이션이다.
@Target
어노테이션의 적용대상을 정의한다. TYPE(클래스, 인터페이스, enum), TYPE_PARAMETER (타입 매개변수), FIELD(필드), CONSTRUCTOR(생성자) 등이 있다. 많으므로 필요할 때마다 확인하는 것도 좋다.
@Retention
어노테이션의 유지기간을 지정한다.
- SOURCE | 소스파일에만 존재하고 클래스파일에는 없음
@Override, @SuppressWarning처럼 컴파일러가 확인해야하는 경우에 해당한다.
- CLASS | 클래스 파일에 존재하지만 실행 중에서는 사용불가. 기본값
컴파일러가 클래스파일에 어노테이션 정보를 저장할 수 있으나 JVM이 이를 알지 못해 잘 사용되지 않는다. SOURCE 나 RUNTIME을 주로 사용한다고.
- RUNTIME | 실행시 사용 가능
@FunctionalInterface의 경우에는 컴파일러가 체크하지만 실행 시에도 사용되므로 여기에 해당한다.
@Documented
어노테이션의 정보가 javadoc으로 작성한 문서에 포함되도록한다.
@Ingerited
어노테이션이 하위클래스에 상속되도록한다. 이 어노테이션이 붙여진 어노테이션을 사용하면 하위클래스에도 적용된다.
@Repeatable
어노테이션은 보통 하나의 대상에 한 종류의 어노테이션을 사용하는데(여러 개를 사용하면 에러가 발생한다.), @Repeatable이 붙은 어노테이션의 경우에는 여러 번 사용할 수 있다.
@Repeatable(ToDos.class)
@interface Todo{
String value();
}
이렇게 정의가 되어있다면 (커스텀 어노테이션을 사용하는 방법은 나중에 다룰 예정이다.)
@Todo("ToDoToDo")
@Todo("tOdOtOdO")
public static void main(String[] args) throws InterruptedException { ... }
이렇게 여러 개의 어노테이션을 부착할 수 있다.
@interface ToDos{
Todo[] value();
}
여기서 ToDos는 여러 Todo 어노테이션을 하나로 묶어서 다룰 수 있는 어노테이션이다. Todo의 배열타입을 반드시 이름이 value가 되도록 설정해야한다.
왜 ToDos가 필요하냐면 자바는 하나의 대상에 동일한 타입의 어노테이션을 하나씩만 저장하도록 설계되어 어노테이션을 여러 개 사용할 수 없기 때문이다. 그래서 Todos를 사용해, 컴파일과정에서 @Todo 어노테이션들을 @Todo 어노테이션의 배열을 포함한 @ToDos 어노테이션으로 변경하고, 그 배열에 접근해 어노테이션들을 읽는다.
@ToDos({
@Todo("ToDoToDo")
@Todo("tOdOtOdO")
})
public static void main(String[] args) throws InterruptedException { ... }
컴파일과정에서 이렇게 변환되어서 @Todos 안의 배열에 접근해 @Todo 어노테이션들을 읽는 방식으로 어노테이션을 여러 개 적용시킬 수 있게되는 것이다.
@Native
@Native public static final long MIN_VALUE = 0x8000000000000000L;
네이티브 메서드에 의해 참조되는 상수필드를 나타낸다. 위는 Long 클래스에 정의된 @Native 어노테이션
말이 좀 어려운데 그냥 네이티브 메서드에서 참조할 상수를 선언해둔 것이다.
@IntrinsicCandidate
public native int hashCode();
네이비트 메서드는 JVM이 설치된 OS의 메서드다. 주로 C를 통해 작성되며, 자바에서는 메서드 선언부만 정의한다. 물론 이렇게 정의한 메서드와 OS의 메서드를 연결해주어야한다.
즉, @Native 어노테이션이 붙여진 변수는 해당 변수를 네이티브 메서드에서 사용하겠다는 의미와 동일하다.
커스텀 어노테이션 사용해보기
이번에는 어노테이션을 직접 만들어 사용해볼 것이다.
@interface 어노테이션이름 {
...내용...
}
기본형은 다음과 같다.
내용 부분에 메서드가 들어올 수 있는데, 이 메서드들을 요소라고 칭한다.
@interface TestInfo {
int count();
String testBy();
DateTime testDate();
}
@interface DateTime {
String yymmdd();
String hhmmss();
}
이렇게. 각 내부 메서드들이 요소가 되며, 요소는 자신이 아닌 다른 어노테이션을 포함할 수 있다.
어노테이션을 설정할 때에는 인터페이스와 같이 메서드 선언부만 있는 추상메서드 형태로 선언된다.
@TestInfo(
count = 1,
testBy = "ColorIt",
testDate = @DateTime(yymmdd = "251231", hhmmss = "180433")
)
public static void main(String[] args) throws InterruptedException
그리고 나중에 사용할 때 값만 빠짐없이 넣어주면 된다. 순서는 상관없다.
@interface TestInfo {
int count() default 1;
String testBy() default "ColorIt";
DateTime testDate();
}
default 키워드를 사용해서 기본값을 지정할 수도 있다.
어노테이션 요소는
- 요소의 타입이 기본형, String, enum, 어노테이션, Class만 해당한다.
- 괄호 안에 매개변수 사용불가
- 예외처리 불가
- 제너릭을 사용할 수 없다.
등의 규칙을 지켜야한다.
마커 어노테이션
@Serializable이나 @Clonable 인터페이스처럼, 요소가 하나도 정의되지 않은 어노테이션이 있다. 이 어노테이션은 단지 특정한 용도를 알려주는 역할을 담당한다. 이를 마커 어노테이션이라고 칭한다.
이번에는 어노테이션에 대해서 알아보았다.
어노테이션은 개발자 또는 컴파일러가 해당 메서드, 변수, 클래스 등이 어떠한 기능을하는지 알려주는 역할을하며, 그 외에는 프로그램에 어떠한 역할을 끼치지 않는, 일종의 주석과도 비슷한 역할을 한다.
어노테이션 내부에서는 추상메서드를 선언할 수 있으며, (매개변수는 선언할 수 없다.) 선언된 메서드들은 default 설정이 되어있지 않는 이상 어노테이션을 사용하는 곳에서 메서드의 값을 지정해주어야한다.
'언어공부 > Java | Kotlin' 카테고리의 다른 글
| [Java] 자바의 정석 독서 #30 - 열거형 (0) | 2025.12.31 |
|---|---|
| [Java] finalize()는 무엇이고, 왜 지양해야하는가? (0) | 2025.12.25 |
| [Java] 자바 내에서 참조란 무엇인가? (0) | 2025.12.25 |
| [Java] 자바의 정석 독서 #29 - 제너릭 메서드, 제너릭 형변환 (0) | 2025.12.24 |
| [Java] 자바의 정석 독서 #28 - 제너릭 와일드카드, 공변성 (1) | 2025.12.23 |
