[Java] Annotation(Java Annotation, Lombok Annotation)

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/

https://haenny.tistory.com/387

https://www.daleseo.com/lombok-popular-annotations/

'Back > 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