2021. 2. 26.

14주차 과제: 제네릭

https://github.com/whiteship/live-study/issues/14

목표

자바의 제네릭에 대해 학습하세요.

학습할 것 (필수)

  • 제네릭 사용법
  • 제네릭 주요 개념 (바운디드 타입, 와일드 카드)
  • 제네릭 메소드 만들기
  • Erasure


제네릭 사용법

2004년 J2SE(Java 2 Platform, Standard Edition. 6.0 버전 이후 'Java SE'로 변경) 5.0 버전에 추가된 기능입니다. 자바의 타입 시스템을 확장해 "컴파일 시 타입 안정성을 제공하면서 다양한 타입의 객체에서 작동하는 타입 또는 메소드"를 제공하도록 설계되었습니다.

Java 컬렉션 프레임 워크는 컬렉션 인스턴스에 저장된 오브젝트 타입을 지정하는 제네릭을 지원합니다.

다음 Java 코드는 제네릭을 사용하지 않을 때 존재하는 문제를 보여줍니다.

import java.util.List;
import java.util.ArrayList;

public class Main {

    public static void main(String[] args) {
        List list = new ArrayList();
        list.add("hello"); // 문자열은 정수 타입으로 형 변환이 불가능합니다.
        Integer i = (Integer) list.get(0); // 런타임 오류
    }
}
Exception in thread "main" java.lang.ClassCastException: 
    class java.lang.String cannot be cast to class java.lang.Integer 
    (java.lang.String and java.lang.Integer are in module java.base of loader 'bootstrap')
	at com.testcompany.Main.main(Main.java:11)

코드는 오류 없이 컴파일되지만, 메인 코드의 세 번째 줄을 실행할 때 런타임 예외(java.lang.ClassCastException)가 발생합니다. 이러한 유형의 논리 오류를 제네릭을 사용하여 컴파일 시간에 감지할 수 있으며 이를 사용하는 주요 동기입니다.

import java.util.List;
import java.util.ArrayList;

public class Main {

    public static void main(String[] args) {
        List<String> v = new ArrayList<String>();
        v.add("test");
        Integer i = (Integer) v.get(0); // (타입 오류)  컴파일 오류
    }
}

cast(형 변환) 제거

다음 코드는 제네릭을 사용함으로써 캐스팅이 불필요해짐을 보여줍니다.

import java.util.List;
import java.util.ArrayList;

public class Main {

    public static void main(String[] args) {
        List list = new ArrayList();
        list.add("hello");
        Strign s = (String) list.get(0);
    }
}
import java.util.List;
import java.util.ArrayList;

public class Main {

    public static void main(String[] args) {
        List<String> list = new ArrayList<String>();
        list.add("hello");
        String s = list.get(0); // 형 변환 불필요
    }
}


- 간단한 상자 클래스

public class Box {
    private Object object;

    public void set(Object object) { this.object = object; }
    public Object get() { return object; }
}

컴파일 시간에는 클래스가 어떻게 사용됐는지 확인할 방법이 없습니다. Integer 타입이 통과하고 다른 파트에서 실수로 String 값을 통과 시켜 런타임 에러를 발생할 수 있습니다.

- 제네릭 버전 상자 클래스

제네릭 클래스는 다음과 같은 형식으로 정의합니다.

class name<T1, T2, ..., Tn> { /* ... */ }

클래스 이름 다음으로 홑화살괄호(부등호 기호)로 구분된 타입 매개변수(parameter)가 있습니다. 타입 매개변수(타입 변수라고도 합니다.)는 'T1, T2, ..., Tn'처럼 열거합니다.

public class Box<T> {
    private T t;

    public void set(T t) { this.t = t; }
    public T get() { return t; }
}

Object는 모두 T로 대체됩니다. 똑같은 테크닉으로 제네릭 인터페이스도 만들 수 있습니다.

- 타입 파라미터 명명 규칙

규칙에 따라 타입 파라미터(유형 매개변수) 이름은 단일 대문자입니다. 이미 알고 있는 변수 명명 규칙과 다르지만 그럴만한 이유가 있습니다. 이 규칙이 없으면 타입 변수와 일반 클래스 또는 인터페이스 이름의 차이를 구분하기 어려울 겁니다.

가장 일반적으로 사용되는 타입 파라미터 이름은 다음과 같습니다.

  • E - Element (자바 컬렉션 프레임워크에서 광범위하게 사용)
  • K - Key
  • N - Number
  • T - Type
  • V - Value
  • S, U, V etc. - 2nd, 3rd, 4th types

- 제네릭 유형 호출 및 인스턴스화

코드 내에서 제네릭 박스 클래스를 참조하려면 T를 Integer와 같은 구체적인 값으로 대체해 호출해야 합니다.

Box<Integer> integerBox;

클래스를 인스턴스화 하려면 평소처럼 new 키워드를 사용하되 클래스 이름과 괄호 사이에 <Integer>를 넣습니다.

Box<Integer> integerBox = new Box<Integer>

- 다이아몬드

Java SE 8 이상부터 컴파일러가 컨텍스트에서 타입 인수(arguments)를 결정하거나 추론할 수 있으면 제네릭 클래스에서 생성자를 호출하는데 필요한 타입 인수를 (<>) 세트로 바꿀 수 있습니다. 비공식적으로 다이아몬드라고 합니다.

Box<Integer> integerBox = new Box<>();

출처:
https://en.wikipedia.org/wiki/Generics_in_Java
https://docs.oracle.com/javase/tutorial/java/generics/why.html


제네릭 주요 개념 (바운디드 타입, 와일드 카드)

- 바운디드 타입 파라미터

타입 인수(type arguments)를 제한하려는 경우에 사용합니다. 예를 들어 숫자에 작동하는 메소드는 Number 또는 subclass 인스턴스만 허용하려고 할 수 있습니다. 이것이 바운디드 타입 파라미터(경계 유형 매개변수)의 용도입니다.

바운디드 타입 파라미터를 선언하려면 타입 파라미터의 이름, extends 키워드, 상위 경계(upper bound)(예시에서는 Number)를 나열합니다.

이 컨텍스트에서 extends는 확장(extends;클래스에서 사용되는) 또는 구현(implements;인터페이스에서 사용되는)의 일반적인 의미로 사용됩니다.

public <U extends Number> void inspect(U u){
    System.out.println("T: " + t.getClass().getName());
    System.out.println("U: " + u.getClass().getName());
}

바운드에 정의된 메소드 실행도 가능합니다.

public class NaturalNumber<T extends Integer> {

    private T n;

    public NaturalNumber(T n)  { this.n = n; }

    public boolean isEven() {
        return n.intValue() % 2 == 0;
    }

    // ...
}

isEven 메소드는 n을 사용해 Integer 클래스에 정의된 intValue 메소드를 호출합니다.

- 와일드 카드

제네릭 코드에서 와일드카드라고 하는 물음표(?)는 알 수 없는 타입을 나타냅니다. 와일드카드는 파라미터, 필드, 지역 변수의 타입 등 다양한 상황에서 사용할 수 있습니다.

public static void process (List <? extends Number> list) {/ * ... * /}

상한 제한 와일드카드(Upper Bounded Wildcards)는 예시처럼 Number 타입 또는 상속받는 subclass를 사용합니다.

public static void printList (List <?> list) {/ * ... * /}

무제한 와일드카드(Unbounded Wildcards)는 <?>를 사용합니다.

public static void addNumbers (List <? super Integer> list) {/ * ... * /}

하위 제한 와일드카드(Lower Bounded Wildcards)는 해당 타입과 superclass로 제한합니다.


제네릭 메소드 만들기

제네릭 메소드는 자체 타입 파라미터(형식 매개변수)를 사용합니다. 제네릭 타입을 선언하는 것과 비슷하지만 타입 파라미터 제한은 메소드 범위까지만 제한됩니다. 제네릭 클래스 생성자뿐만 아니라 static, non-static 제네릭 메소드도 사용 가능합니다.

public class Util {
    public static <K, V> boolean compare(Pair<K, V> p1, Pair<K, V> p2) {
        return p1.getKey().equals(p2.getKey()) &&
               p1.getValue().equals(p2.getValue());
    }
    // ...
}

메소드를 호출하는 구문은 다음과 같습니다.

Pair<Integer, String> p1 = new Pair<>(1, "apple");
Pair<Integer, String> p2 = new Pair<>(2, "pear");
boolean same = Util.<Integer, String>compare(p1, p2);

// 타입을 생략 가능하며 컴파일러가 필요한 타입을 추론합니다.
boolean same = Util.compare(p1, p2);


Erasure

제네릭을 구현하기 위해 Java 컴파일러는 다음과 같은 상황에서 타입 제거(type erasure)를 적용합니다.

  • 제네릭 타입의 모든 타입 파라미터를 해당 바운드나 Object(unbounded 경우)로 교체합니다. 그러므로 생성된 바이트 코드에는 일반 클래스, 인터페이스 및 메소드만 포함됩니다.
  • 타입 안전을 유지하기 위해 필요한 경우 타입 캐스트를 삽입합니다.
  • 확장된 제네릭 타입의 다형성(polymorphism)을 보존하는 브릿지(bridge) 메소드를 생성합니다.

타입 제거(type erasure)는 parameterized(매개변수화) 타입에 새 클래스가 생성되지 않도록 합니다. 결과적으로 런타임 오버헤드를 발생시키지 않습니다.

public class Node<T> {

    private T data;
    private Node<T> next;

    public Node(T data, Node<T> next) {
    // ...

타입 파라미터 T가 unbounded이기 때문에 컴파일러는 Object로 교체합니다.

public class Node {

    private Object data;
    private Node next;

    public Node(Object data, Node next) {
    // ...

다음 예제는 제한된(bounded) 타입 파라미터를 사용합니다.

public class Node<T extends Comparable<T>> {

    private T data;
    private Node<T> next;

    public Node(T data, Node<T> next) {
    // ...

컴파일러는 bounded 타입 파라미터 T를 첫번째 바운드 클래스 Comparable로 교체합니다.

public class Node {

    private Comparable data;
    private Node next;

    public Node(Comparable data, Node next) {
    // ...

타입 제거 프로세스 일부로 브리지 메소드를 만들어야 할 수 있습니다. 일반적으로 브리지 메소드에 대해 걱정할 필요는 없지만, 스택 추적에 나타나는 경우 당황할 수 있습니다.

public class Node {

    public Object data;

    public Node(Object data) { this.data = data; }

    public void setData(Object data) {
        System.out.println("Node.setData");
        this.data = data;
    }
}

public class MyNode extends Node {

    public MyNode(Integer data) { super(data); }

    public void setData(Integer data) {
        System.out.println("MyNode.setData");
        super.setData(data);
    }
}

타입 삭제 후 메소드의 시그니쳐가 일치하지 않습니다. Node 메소드는 setData(Object)가 되고 MyNode 메소드는 setData(Integer)가 됩니다. 따라서 MyNode setData 메소드는 Node setData 메소드를 재정의하지 않습니다.

이 문제를 해결하고 다형성을 보존하기 위해 컴파일러는 subtype이 예상대로 작동하는지 확인하는 브릿지 메소드를 생성합니다.

class MyNode extends Node {

    // Bridge method generated by the compiler
    //
    public void setData(Object data) {
        setData((Integer) data);
    }

    public void setData(Integer data) {
        System.out.println("MyNode.setData");
        super.setData(data);
    }

    // ...
}

출처: https://docs.oracle.com/javase/tutorial/java/generics/erasure.html

2021. 2. 19.

13주차 과제: I/O

https://github.com/whiteship/live-study/issues/13

목표

자바의 Input과 Ontput에 대해 학습하세요.

학습할 것 (필수)

  • 스트림 (Stream) / 버퍼 (Buffer) / 채널 (Channel) 기반의 I/O
  • InputStream과 OutputStream
  • Byte와 Character 스트림
  • 표준 스트림 (System.in, System.out, System.err)
  • 파일 읽고 쓰기


스트림 (Stream) / 버퍼 (Buffer) / 채널 (Channel) 기반의 I/O

스트림

  • 입력에서 출력으로 흐르는 흐름 (단방향)
  • FIFO(First In First Out)
  • 블록킹 방식만 지원(동기)
  • 입력 스트림, 출력 스트림 필요

버퍼

  • 기본 데이터 타입을 저장할 수 있는 저장소
  • 일정한 양이 채워지면 데이터를 전송

채널

  • 쌍방향 통로
  • 데이터를 주고받을 때 버퍼를 사용
  • 입력과 출력을 위한 별도의 채널 불필요

NIO (New I/O, Non-blocking I/O)

  • 자바 1.4 버전부터 추가된 API
  • Buffer(버퍼)를 사용
  • 비동기 방식 지원
  • 스트림이 아닌 채널(Channel)을 사용


InputStream과 OutputStream

InputStream

  • 바이트 단위 입출력을 위한 최상위 입력 스트림 클래스
  • FileInputStream, BufferedInputStream, DataInputStream

표 출처: https://coding-factory.tistory.com/281


OutputStream

  • 바이트 단위 입출력을 위한 최상위 입력 스트림 클래스
  • FileOutputStream, PrintStream, BufferedOutputStream, DataOutputStream

표 출처: https://coding-factory.tistory.com/281


Byte와 Character 스트림

Byte Stream

  • 바이트 기반 입출력 스트림
  • 입출력 단위가 1byte
  • FileInputStream/FileOutputStream: 파일
  • ByteArrayInputStream/ByteArrayOutputStream: 메모리(byte배열)
  • PipedInputStream/PipedOutputStream: 프로세스(프로세스간 통신)
  • AudioInputStream/AudioOutputStream: 오디오 장치

Character Stream

  • 문자(2 byte)를 위한 스트림
  • 모든 문자 스트림 클래스는 Reader / Writer의 자손
  • 스트림 클래스로 수행 된 입력 및 출력은 로컬 문자 집합과 자동으로 변환

https://docs.oracle.com/javase/tutorial/essential/io/charstreams.html


표준 스트림 (System.in, System.out, System.err)

콘솔을 통한 데이터 입력과 출력을 위한 스트림

  • System.in - 콘솔로부터 데이터 입력받는데 사용
  • System.out - 콘솔 화면에 문자열 출력을 위한 용도
  • System.err - System.out과 같이 콘솔 문자열 출력이지만 버퍼링을 지원하지 않고 빨간색으로 출력된다.


파일 읽고 쓰기

채널 I/O를 사용한 파일 읽고 쓰는 방법

스트림 I/O가 한 번에 문자를 읽는 동안 채널 I/O는 한 번에 버퍼를 읽습니다. ByteChannel 인터페이스는 기본으로 read write 기능을 제공합니다. SeekableByteChannel은 ByteChannel의 위치를 유지하거나 변경할 수 있는 기능이 있습니다. 또한 채널과 관련된 파일 자르기 및 파일 크기 쿼리를 지원합니다.

채널 I/O를 읽고 쓰는 방법에는 두 가지가 있습니다.

  • newByteChannel(Path, OpenOption...)
  • newByteChannel(Path, <Set? extends OpenOption>, FileAttribute<?>...)

다음 코드 조각은 파일을 읽고 표준 출력으로 인쇄합니다.

// Defaults to READ
try (SeekableByteChannel sbc = Files.newByteChannel(file)) {
    ByteBuffer buf = ByteBuffer.allocate(10);

    // Read the bytes with the proper encoding for this platform.  If
    // you skip this step, you might see something that looks like
    // Chinese characters when you expect Latin-style characters.
    String encoding = System.getProperty("file.encoding");
    while (sbc.read(buf) > 0) {
        buf.rewind();
        System.out.print(Charset.forName(encoding).decode(buf));
        buf.flip();
    }
} catch (IOException x) {
    System.out.println("caught exception: " + x);

UNIX 및 기타 POSIX 파일 시스템 용으로 작성된 다음 예제는 특정 파일 권한 집합을 사용하여 로그 파일을 만듭니다. 이 코드는 로그 파일을 만들거나 이미있는 경우 로그 파일에 추가합니다. 로그 파일은 소유자에 대한 읽기 / 쓰기 권한과 그룹에 대한 읽기 전용 권한으로 생성됩니다.

import static java.nio.file.StandardOpenOption. *; 
import java.nio. *; 
import java.nio.channels. *; 
import java.nio.file. *; 
import java.nio.file.attribute. *; 
import java.io. *; 
import java.util. *; 

public class LogFilePermissionsTest { 

  public static void main (String [] args) { 
  
    // 파일에 추가하기위한 옵션 세트를 만듭니다. 
    Set <OpenOption> options = new HashSet<OpenOption>(); 
    options.add(APPEND); 
    options.add(CREATE); 

    // 사용자 지정 권한 속성을 만듭니다. 
    Set <PosixFilePermission> perms = 
      PosixFilePermissions.fromString ("rw-r-----"); 
    FileAttribute <Set<PosixFilePermission>>
      PosixFilePermissions.asFileAttribute(perms); 

    // 문자열을 ByteBuffer로 변환합니다. 
    String s = "Hello World!"; 
    byte data[] = s.getBytes (); 
    ByteBuffer bb = ByteBuffer.wrap(data); 
    
    경로 파일 = Paths.get ("./permissions.log"); 

    try (SeekableByteChannel sbc = 
      Files.newByteChannel (file, options, attr)) { 
      sbc.write (bb); 
    } catch (IOException x) { 
      System.out.println ("예외 발생: "+ x); 
    } 
  } 
}

출처: https://docs.oracle.com/javase/tutorial/essential/io/file.html

2021. 2. 5.

12주차 과제: 애노테이션

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

목표

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

학습할 것 (필수)

  • 애노테이션 정의하는 방법
  • @retention
  • @target
  • @documented
  • 애노테이션 프로세서


애노테이션 정의하는 방법

// 골뱅이(@, at sign) 기호는 컴파일러에게 주석임을 알려줍니다.
// 예) @재정의
@Override
void mySuperMethod() { ... }

자바 애노테이션(Annotations)이란?

메타 데이터(metadata: 다른 데이터를 설명해 주는 데이터)의 한 형태인 주석(Annotations)은 프로그램의 일부는 아니지만, 프로그램에 대한 데이터를 제공합니다.

주석은 코드 작동에 직접적인 영향을 주지 않습니다.

주석은 다음과 같은 여러 용도가 있습니다.

  • 컴파일러에 대한 정보 - 컴파일러에서 주석을 사용하여 오류를 감지하거나 경고를 억제할 수 있습니다.
  • 컴파일 시간, 배포 시간 처리 - 주석 정보를 처리해 코드, XML 파일 등을 생성할 수 있습니다.
  • 런타임 처리 - 일부 주석은 런타임에 검사 가능
@SuppressWarnings (value = "unchecked") 
void myMethod () {...}

값이 하나만 있으면 다음과 같이 생략 가능합니다.

@SuppressWarnings ("unchecked")

주석 유형이 동일한 경우 반복 주석이라고 합니다.

@Author (name = "Kim") 
@Author (name = "Park") 
class MyClass {...}

반복 주석은 Java SE 8 릴리스부터 지원됩니다.

클래스, 필드, 메소드 및 기타 프로그램 요소 선언에도 주석을 사용할 수 있습니다.

자바 SE 8 릴리스부터 주석을 타입으로 적용할 수도 있습니다.

// 클래스 인스턴스 생성
new @Interned MyObject();

// 타입 캐스트
myString = (@NonNull String) str;

// 구현 절(implements clause)
class UnmodifiableList<T> implements
        @Readonly List<@Readonly T> { ... }

// 예외 처리
void monitorTemperature() throws
        @Critical TemperatureException { ... }

많은 애노테이션이 코드의 주석(코멘트)을 대체합니다.

소프트웨어 개발 그룹이 전통적으로 클래스 시작을 다음과 같이 중요한 정보를 제공하는 주석으로 시작한다고 가정해 봅시다.

public class MyClass extends MySuperClass {

   // Author: Tester1
   // Date: 2/01/2021
   // Current revision: 3
   // Last modified: 1/31/2021
   // By: Kim
   // Reviewers: Alice, Bill, Cindy

}

애노테이션을 사용해 똑같은 메타데이터를 추가합니다. 먼저 애노테이션 타입을 정의합니다.

@interface ClassPreamble {
   String author();
   String date();
   int currentRevision() default 1;
   String lastModified() default "N/A";
   String lastModifiedBy() default "N/A";
   // Note use of array
   String[] reviewers();
}

애노테이션 타입 정의는 인터페이스 정의와 비슷하게 interface 키워드 앞에 @를 붙입니다. 애노테이션 타입은 인터페이스의 한 형태입니다.

애노테이션 타입을 정의한 후 다음과 같이 값을 채워 타입으로 사용할 수 있습니다.

@ClassPreamble (
   author = "Tester1",
   date = "2/01/2021",
   currentRevision = 3,
   lastModified = "1/31/2021",
   lastModifiedBy = "Kim",
   reviewers = {"Alice", "Bob", "Cindy"}
)
public class MyClass extends MySuperClass { ... }

참고: @ClassPreamble의 정보가 Javadoc 문서에 나타나도록 하려면 @ClassPreamble 정의에 @Documented 주석을 추가해야 합니다.

// @Documented 사용하기 위해 import
import java.lang.annotation.*;

@Documented
@interface ClassPreamble {
   // 애노테이션 요소 정의
}

출처:
https://docs.oracle.com/javase/tutorial/java/annotations/


@Retention

애노테이션 타입이 있는 애노테이션이 얼마나 보존될지를 나타냅니다. 타입 선언에 Retention 애노테이션이 없는 경우 retention 정책 기본값은 RetentionPolicy.CLASS 입니다.

  • RetentionPolicy.SOURCE - 소스 수준에서만 유지되며 컴파일러에서 무시됩니다.
  • RetentionPolicy.CLASS - 컴파일 타임에 컴파일러에 의해 유지되지만 JVM에서는 무시됩니다.
  • RetentionPolicy.RUNTIME - JVM에 의해 유지되므로 런타임 환경에서 사용할 수 있습니다.

Retention.java

package java.lang.annotation;

/**
 * Indicates how long annotations with the annotated type are to
 * be retained.  If no Retention annotation is present on
 * an annotation type declaration, the retention policy defaults to
 * {@code RetentionPolicy.CLASS}.
 *
 * A Retention meta-annotation has effect only if the
 * meta-annotated type is used directly for annotation.  It has no
 * effect if the meta-annotated type is used as a member type in
 * another annotation type.
 *
 * @author  Joshua Bloch
 * @since 1.5
 * @jls 9.6.4.2 @Retention
 */
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Retention {
    /**
     * Returns the retention policy.
     * @return the retention policy
     */
    RetentionPolicy value();
}
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

public class MyTest {
    public static void main(String[] args) {
    
    @Retention(RetentionPolicy.RUNTIME)
//    @Retention(RetentionPolicy.CLASS)
//    @Retention(RetentionPolicy.SOURCE)
    @Target(ElementType.METHOD)
    /* @Target({
            ElementType.PACKAGE, // 패키지 선언시
            ElementType.TYPE, // 타입 선언시
            ElementType.CONSTRUCTOR, // 생성자 선언시
            ElementType.FIELD, // 멤버 변수 선언시
            ElementType.METHOD, // 메소드 선언시
            ElementType.ANNOTATION_TYPE, // 어노테이션 타입 선언시
            ElementType.LOCAL_VARIABLE, // 지역 변수 선언시
            ElementType.PARAMETER, // 매개 변수 선언시
            ElementType.TYPE_PARAMETER, // 매개 변수 타입 선언시
            ElementType.TYPE_USE // 타입 사용시
    }) */
    public @interface MyAnnotation {
        /* enum 타입을 선언할 수 있습니다. */
        public enum Quality {BAD, GOOD, VERYGOOD}
        /* String은 기본 자료형은 아니지만 사용 가능합니다. */
        String value();
        /* 배열 형태로도 사용할 수 있습니다. */
        int[] values();
        /* enum 형태를 사용하는 방법입니다. */
        Quality quality() default Quality.GOOD;
    }
}

코드 출처: https://jdm.kr/blog/216

출처:
https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/lang/annotation/Retention.html

https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/lang/annotation/Target.html

https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/lang/annotation/ElementType.html


@Target

애노테이션을 어디에 적용할 수 있는지 나타내는 컨텍스트입니다.

@Target 메타-애노테이션은 선언된 유형 자체가 메타 주석 유형임을 나타냅니다. 주석 유형 선언에서만 사용할 수 있습니다.

    @Target(ElementType.ANNOTATION_TYPE)
    public @interface MetaAnnotationType {
        ...
    }

이 @Target 메타 주석은 복잡한 주석 유형 선언에서 맴버 유형으로 사용하기위한 것입니다. 직접 주석을 다는데는 사용할 수 없습니다.

    @Target({})
    public @interface MemberType {
        ...
    }

ElementType 상수가 @Target 애노테이션에 두 번 이상 나타나는 것은 컴파일 타임 오류입니다.

    @Target({ElementType.FIELD, ElementType.METHOD, ElementType.FIELD})
    public @interface Bogus {
        ...
    }

출처: https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/lang/annotation/Target.html


@Documented

'Documented 주석 처리된 경우 javadoc과 같은 기본 도구는 출력시 해당 유형의 주석을 표시하지만 Documented가 없는 주석은 표시되지 않습니다.'

javadoc(소스 코드의 문서 주석을 HTML 형식으로 API 문서를 생성하는 도구)으로 api 문서를 만들 때 어노테이션에 대한 설명도 포함하도록 지정하는 메타테그

주석 포맷 설명: https://www.oracle.com/technical-resources/articles/java/javadoc-tool.html#format

출처: https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/lang/annotation/Documented.html


애노테이션 프로세서

애노테이션 프로세서는 컴파일 타임에 주석을 스캔하고 처리하기 위해 javac로 빌드된 도구입니다.

주석을 만들고 처리하는 단계

  • 주석 선언
  • 주석 프로세서 구현
  • 코드에서 주석 사용

AbstractProcessor

package com.example;

public class MyProcessor extends AbstractProcessor {

	@Override
	public synchronized void init(ProcessingEnvironment env){ }

	@Override
	public boolean process(Set<? extends TypeElement> annoations, RoundEnvironment env) { }

	@Override
	public Set<String> getSupportedAnnotationTypes() { }

	@Override
	public SourceVersion getSupportedSourceVersion() { }

}
import javax.annotation.processing.*;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.Element;
import javax.lang.model.element.TypeElement;
import javax.tools.Diagnostic;
import java.util.Set;

@SupportedAnnotationTypes("Todo")   // (1)
@SupportedSourceVersion(SourceVersion.RELEASE_8)   // (2)
public class TodoProcessor extends AbstractProcessor{   // (3)
    // (4)
    public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
        
    }
}

(1) SupportedAnnotationTypes는 클래스가 처리할 주석의 유형을 알려주는 메타 주석입니다. 이 경우 클래스는 Todo 주석을 처리합니다.

(2) SupportedSourceVersion 이 주석 프로세서가 지원하는 최신 Java 버전입니다.

(3) 주석 프로세서는 Processor 인터페이스를 구현 하거나 AbstractProcessor 클래스를 확장해야 합니다.

(4) AbstractProcessor 클래스의 프로세스 메서드를 재정의 합니다 . 컴파일러는 응용 프로그램의 모든 클래스에서이 메서드를 호출합니다.

출처:
https://docs.oracle.com/en/java/javase/11/docs/api/java.compiler/javax/annotation/processing/AbstractProcessor.html

https://thetechstack.net/how-to-process-annotations-in-java/

https://www.baeldung.com/java-annotation-processing-builder

https://iammert.medium.com/annotation-processing-dont-repeat-yourself-generate-your-code-8425e60c6657

http://hannesdorfmann.com/annotation-processing/annotationprocessing101/

https://medium.com/@jason_kim/annotation-processing-101-%EB%B2%88%EC%97%AD-be333c7b913