Annotation이란?
= 메타 데이터 (meta Data)
: 애플리케이션이 처리해야할 데이터가 아니라, 컴파일 과정과 실행과정에서 코드를 어떻게 컴파일하고 처리할 것인지 알려주는 정보
아래와 같은 형태로 작성된다.
@Annotation
Annotation 용도
- 컴파일러에게 코드 문법에러를 체크하도록 정보를 제공
- 소프트웨어 개발 툴이 빌드나 배치 시 코드를 자동으로 생성할 수 있도록 정보를 제공
- 실행 시 (런타임 시) 특정 기능을 실행하도록 정보를 제공
컴파일러에게 코드문법 에러를 체크하도록 정보를 제공하는 대표적인 예는 @Overrride 어노테이션이다.
@Override는 메서드 선언 시 사용하는데, 메소드가 오버라이드(재정의) 된 것임을 컴파일러에게 알려줘 컴파일러가 오버라이드 검사를 하도록 해준다.
정확히 오버라이드가 되지 않았다면 컴파일러는 에러를 발생시킨다.
어노테이션은 빌드 시 자동으로 XML 설정 파일을 생성하거나, 배포를 위해 JAR 압축 파일을 생성하는데에도 사용된다.
그리고 실행 시 클래스의 역할을 정의하기도 한다.
Annotation 타입정의와 적용
어노테이션 타입을 정의하는 방법은 인터페이스를 정의하는 것과 유사하다.
다음과 같이 @interface 를 사용해서 어노테이션을 정의하며, 그 뒤에 사용할 어노테이션 이름이 온다.
public @interface Annotation{
}
이렇게 정의한 어노테이션은 코드에서 다음과 같이 사용한다.
@AnnotationName
어노테이션은 엘리먼트(element)를 멤버로 가질 수 있다.
각 엘리먼트는 타입과 이름으로 구성되며, 디폴트값을 가질 수 있다.
public @interface AnnotationName{
타입 elementName()[default값]; // 엘리먼트 선언
}
엘리먼트의 타입으로는 int나 double과 같은 기본 데이터타입이나 String, 열거타입, Class타입, 그리고 이들의 배열타입을 사용할 수 있다. 엘리먼트의 이름 뒤에는 메소드를 작성하는 것처럼 ()를 붙여야 한다.
Java 표준 Annotation
- @Override
- 선언한 메서드가 오버라이드 되었다는 것을 컴파일러에게 정보 제공
- 만약 상위(부모) 클래스(또는 인터페이스)에서 해당 메서드를 찾을 수 없다면 컴파일 에러를 발생
- @Deprecated
- 해당 메서드가 더 이상 사용되지 않음을 표시, 다른 개발자들에게 사용하지 말 것을 권장
- 만약 사용할 경우 컴파일 경고를 발생
- @FunctionalInterface
- 함수형 인터페이스의 < 하나의 추상 메서드만 가져야 한다> 는 제약을 확인 해주는 역할
- 함수형 인터페이스라는 것을 알림
- @SuppressWarnings
- 컴파일러의 경고메세지가 나타나지 않게 억제
- 사용방법
- 컴파일러(javac.exe)가 사용하는 어노테이션
- 괄호()안에 억제하고자 하는 경고의 종류를 문자열로 지정
- 컴파일러의 경고메세지가 나타나지 않게 억제하여, 프로그래머가 이미 해당 경고를 확인했다는 것을 프로그램에 알린다.
- @SafeVarargs
- Java7 부터 지원, 제너릭 같은 가변인자의 매개변수를 사용할 때의 경고를 무시한다.
기타 Annotation 에 적용되는 Annotation (metaAnnotation)
- @Retention
- 자바 컴파일러가 어노테이션을 다루는 방법을 기술하며, 특정 시점까지 영향을 미치는지를 결정한다.
- 종류
- RetentionPolicy.SOURCE : 컴파일 전까지만 유효. (컴파일 이후에는 사라짐)
- RetentionPolicy.CLASS : 컴파일러가 클래스를 참조할 때까지 유효.
- RetentionPolicy.RUNTIME : 컴파일 이후에도 JVM에 의해 계속 참조가 가능. (리플렉션 사용)
- @Documented
- 해당 어노테이션을 Javadoc에 포함시킵니다.
- @Target
- 어노테이션이 적용할 위치를 선택한다.
- 종류
- ElementType.PACKAGE : 패키지 선언
- ElementType.TYPE : 타입 선언
- ElementType.ANNOTATION_TYPE : 어노테이션 타입 선언
- ElementType.CONSTRUCTOR : 생성자 선언
- ElementType.FIELD : 멤버 변수 선언
- ElementType.LOCAL_VARIABLE : 지역 변수 선언
- ElementType.METHOD : 메서드 선언
- ElementType.PARAMETER : 전달인자 선언
- ElementType.TYPE_PARAMETER : 전달인자 타입 선언
- ElementType.TYPE_USE : 타입 선언
- @Inherited
- 어노테이션의 상속
- @Repeatable
- Java8 부터 지원, 연속적인 Annotation 선언 가능
Lombok 라이브러리
Lombok 라이브러리는 VO(Value Object) 나 DTO(Data Transfer Object) 생성 시
코드를 혁신적으로 줄여주는 라이브러리이다.
Lombok Annotation
- Val
실제로 유형을 작성하는 대신 지역 변수 선언의 유형으로 사용할 수 있다.
이렇게 하면 이니셜라이저 표현식에서 유형이 유추된다.
지역변수도 final로 만들어진다.
이 기능은 필드가 아닌 지역변수 및 foreach 루프에서만 작동된다.
import java.util.ArrayList;
import java.util.HashMap;
import lombok.val;
public class ValExample {
public String example() {
val example = new ArrayList<String>();
example.add("Hello, World!");
val foo = example.get(0);
return foo.toLowerCase();
}
public void example2() {
val map = new HashMap<Integer, String>();
map.put(0, "zero");
map.put(5, "five");
for (val entry : map.entrySet()) {
System.out.printf("%d: %s\n", entry.getKey(), entry.getValue());
}
}
}
- Var
지역변수가 표시되지 않는 다는 점을 제외하면 Val 과 정확히 동일하게 작동한다.
- @NonNull
자동으로 null 체크를 진행하여 null 인 경우 NullPointerException 을 발생 시킨다.
import lombok.NonNull;
public class NonNullExample extends Something {
private String name;
public NonNullExample(@NonNull Person person) {
super("Hello");
this.name = person.getName();
}
}
- @Cleanup
코드 실행 경로가 현재 범위를 벗어나기 전에 지정된 리소스가 자동으로 정리되도록 하는 데 사용할 수 있다 .
close() 메소드를 호출하여 자원을 종료시킨다.
import lombok.Cleanup;
import java.io.*;
public class CleanupExample {
public static void main(String[] args) throws IOException {
@Cleanup InputStream in = new FileInputStream(args[0]);
@Cleanup OutputStream out = new FileOutputStream(args[1]);
byte[] b = new byte[10000];
while (true) {
int r = in.read(b);
if (r == -1) break;
out.write(b, 0, r);
}
}
}
- @Getter/@Setter
모든 필드에 주석을 달아 @Setterlombok이 기본 getter/setter를 자동으로 생성한다.
Entity 클래스 내에서는 Setter를 만들지 않도록 하자.
해당 필드에 변경이 필요하면 그 목적과 의도를 나타낼 수 있는 메소드를 추가해야한다.
import lombok.AccessLevel;
import lombok.Getter;
import lombok.Setter;
public class GetterSetterExample {
/**
* Age of the person. Water is wet.
*
* @param age New value for this person's age. Sky is blue.
* @return The current value of this person's age. Circles are round.
*/
@Getter @Setter private int age = 10;
/**
* Name of the person.
* -- SETTER --
* Changes the name of this person.
*
* @param name The new value.
*/
@Setter(AccessLevel.PROTECTED) private String name;
@Override public String toString() {
return String.format("%s (age: %d)", name, age);
}
}
- @ToString
ToString 메소드를 생성 해준다.
import lombok.ToString;
@ToString
public class ToStringExample {
private static final int STATIC_VAR = 10;
private String name;
private Shape shape = new Square(5, 10);
private String[] tags;
@ToString.Exclude private int id;
public String getName() {
return this.name;
}
@ToString(callSuper=true, includeFieldNames=true)
public static class Square extends Shape {
private final int width, height;
public Square(int width, int height) {
this.width = width;
this.height = height;
}
}
}
- @EqualsAndHashCode
hashCode, equals를 구현해준다.
import lombok.EqualsAndHashCode;
@EqualsAndHashCode
public class EqualsAndHashCodeExample {
private transient int transientVar = 10;
private String name;
private double score;
@EqualsAndHashCode.Exclude private Shape shape = new Square(5, 10);
private String[] tags;
@EqualsAndHashCode.Exclude private int id;
public String getName() {
return this.name;
}
@EqualsAndHashCode(callSuper=true)
public static class Square extends Shape {
private final int width, height;
public Square(int width, int height) {
this.width = width;
this.height = height;
}
}
}
- @NoArgsContructor, @RequiredConstructor and @AllArgsConstructor
인수를 사용하지 않거나 final/Null이 아닌 필드당 하나의 인수를 사용하거나
모든 필드에 대해 하나의 인수를 사용하는 생성자를 생성
- @NoArgsConstructor
매개변수가 없는 생성자 구현 - @RequiredArgsConstructor
final, @NonNull이 있는 필드가 포함된 생성자를 구현 - @AllArgsConstructor
모든 필드를 매개변수로 갖는 생성자를 구현
- @Data
@Getter / @Setter, @RequiredArgsConstructor , @ToString , @EqualsAndHashCode
위 Annotation을 한번에 생성해준다.
import lombok.AccessLevel;
import lombok.Setter;
import lombok.Data;
import lombok.ToString;
@Data public class DataExample {
private final String name;
@Setter(AccessLevel.PACKAGE) private int age;
private double score;
private String[] tags;
@ToString(includeFieldNames=true)
@Data(staticConstructor="of")
public static class Exercise<T> {
private final String name;
private final T value;
}
}
- @Value
@Data와 비슷하지만 불변으로 만들어준다고 생각하면 된다.
한 번 생성하면 변경할 수 없는 불변 객체를 만들기 위한 클래스 선언할 때는 @Data 대신 @Value를 사용하면 된다.
private 접근제어자와 final이 붙은 상수가 된다. 기본 생성자를 만들어주나 private이다.
- @Builder
해당 클래서에 빌드패턴을 적용한 클래스를 생성해준다.
생성자 상단에 선언 시 생성자에게 포함된 필드만 빌더에 포함된다.
생성자 혹은 빌더나 생성시점에 값을 채워주지만 아래와 같은 차이점이 있다.
- 생성자 : 지금 채워야 하는 필드가 무엇인지 정확히 지정할 수 없다.
- 빌더 : 어느 필드에 어떤 값을 채워야 할 지 명확히 인지할 수 있다.
import lombok.Builder;
import lombok.Singular;
import java.util.Set;
@Builder
public class BuilderExample {
@Builder.Default private long created = System.currentTimeMillis();
private String name;
private int age;
@Singular private Set<String> occupations;
}
- @SneakyThrows
메소드 절에서 실제로 선언하지 않고 검사된 예외를 던지며 사용할 수 있다.
그러나 어디서 예외가 발생했는지 알 수 없어 다소 신중하게 사용해야한다.
import lombok.SneakyThrows;
public class SneakyThrowsExample implements Runnable {
@SneakyThrows(UnsupportedEncodingException.class)
public String utf8ToString(byte[] bytes) {
return new String(bytes, "UTF-8");
}
@SneakyThrows
public void run() {
throw new Throwable();
}
}
- @With
final Setter의 복제본을 생성하지만, 변경된 필드가 하나 있는 메서드
작업을 수행하려면 모든 필드에 대한 생성자를 사용한다.
동일한 어휘 순서로 모든 비정적 필드를 포함해야 한다.
import lombok.AccessLevel;
import lombok.NonNull;
import lombok.With;
public class WithExample {
@With(AccessLevel.PROTECTED) @NonNull private final String name;
@With private final int age;
public WithExample(@NonNull String name, int age) {
this.name = name;
this.age = age;
}
}
- @Getter(lazy=true)
Lombok에서 이 getter가 처음 호출될 때 값을 한 번 계산하고 그 이후부터 캐시하는 getter를 생성할 수 있다.
이는 값을 계산할때 많은 CPU가 필요하거나 값에 많은 메모리가 필요한 경우 유용할 수 있다.
이 기능을 사용하려면 변수를 생성하고 private final 실행 비용이 많이 드는 표현식으로 초기화 한 다음 필드에 @Getter(lazy=true) 선언
import lombok.Getter;
public class GetterLazyExample {
@Getter(lazy=true) private final double[] cached = expensive();
private double[] expensive() {
double[] result = new double[1000000];
for (int i = 0; i < result.length; i++) {
result[i] = Math.asin(i);
}
return result;
}
}
- @Log
Lombok이 로거필드를 생성하도록 로그 주석으로 모든 클래스에 주석을 달 수 있다.
로거에는 이름이 지정되며 log 필드 유형은 선택한 로거에 따라 달라진다.
import lombok.extern.java.Log;
import lombok.extern.slf4j.Slf4j;
@Log
public class LogExample {
public static void main(String... args) {
log.severe("Something's wrong here");
}
}
@Slf4j
public class LogExampleOther {
public static void main(String... args) {
log.error("Something else is wrong here");
}
}
@CommonsLog(topic="CounterLog")
public class LogExampleCategory {
public static void main(String... args) {
log.error("Calling the 'CounterLog' with a message");
}
}
참고 문헌 및 사이트
이것이 Java다 _Annotation파트
https://bangu4.tistory.com/199
https://projectlombok.org/features/
'Java' 카테고리의 다른 글
[Java] 제곱계산 반환,홀수만 계산 여러 풀이법 (0) | 2023.02.09 |
---|---|
[Java] String 클래스 (0) | 2023.02.09 |
[Java] 배열과 열거형 (0) | 2023.02.09 |
[Java] 예외처리(try-catch문, throw~) (0) | 2023.02.09 |
[Java] Integer 클래스 (3) | 2023.02.08 |