Chapter 12 지네릭스, 열거형, 애너테이션

1. 지네릭스(Generics)

  • JDK1.5 도입
  • JDK1.8 선택이 아닌 필수

1.1 지네릭스란?

다양한 타입의 객체들을 다루는 메서드나 컬렉션 클래스에 컴파일 시의 타입체크(compile-time type check)를 해주는 기능

  • 타입 안정성을 제공한다.
    • 의도하지 않은 타입의 객체가 저장되는 것을 막아 잘못 형변환 되는 오류를 줄여준다.
  • 형변환의 번거로움을 줄여준다. (타입체크와 형변환 생략 -> 코드 간결)
    • ex) ArrayList클래스: 지네릭스 도입 이전에는 각 객체의 형을 체크해야 했음

1.2 지네릭 클래스의 선언

Box<T>클래스

/* Box<T>클래스 선언 */

// 지네릭 타입 T를 선언
class Box<T> { // class Box {
  // Object item;
  T item;

  void setItem(T item) { // void setItem(Object item) {
    this.item = item;
  }

  T getItem() { // Object getItem() {
    return item;
  }
}

/* Box<T>클래스 사용 */ 

Box<String> b = new Box<String>(); // T 대신 실제 타입 지정

b.setItem(new Object()); // ERROR: String타입 이외의 타입은 지정불가
b.setItem("ABC"); // OK: String타입만 가능

// String item = (String)b.getItem(); 
String item = b.getItem(); // 형변환 필요없음

/* 호환성 */

Box b2 = new Box(); // OK: T는 Object로 간주된다.
b2.setItem("ABC"); // WARN: unchecked or unsafe operation.
b2.setItem(new Object()); // WARN: unchecked or unsafe operation.

T: 타입 변수(type variable)

  • T가 아닌 다른 것도 사용가능하다. (ex. ArrayList<E>, Map<K,V>)
  • 상황에 맞게 의미 있는 문자를 선택해서 사용하는 것이 좋다. (T:type, E:element, K:key, V:value, ...)
  • 기호만 다를 뿐 '임의의 참조형 타입'을 의미한다.

Box<String>클래스

  • Box<T>클래스: 어떤 타입이든 한가지 타입을 정해서 넣을 수 있다.
  • Box<String>클래스: String타입만 담을 수 있다.
class Box<String> { // 지네릭 타입을 String으로 지정
  String item;
  void setItem(String item) {
    this.item = item;
  }
  String getItem() {
    return item;
  }
}

지네릭스의 용어

  • Box<T>: 지네릭 클래스, 'T의 Box' 또는 'T Box' 라고 읽는다.
  • T: 타입변수 또는 타입매개변수, T는 타입문자
  • Box: 원시타입 (raw type)
  • Box<String>: 지네릭 타입 호출, 여기서 String은 매개변수화된 타입(parameterized type) 또는 대입된 타입
  • Box<String>Box<Integer>는 지네릭 클래스 Box<T>에 서로 다른 타입을 대입하여 호출한 것일 뿐 동일한 클래스이며, 컴파일후에 모두 원시타입(Box)로 바뀌어 지네릭 타입이 제거된다.

지네릭스의 제한

  • 모든 객체에 동일하게 동작해야 하는 static멤버에는 타입변수 T를 사용할 수 없다.
    • T는 인스턴스변수로 간주되기 때문이다. static에는 인스턴스변수를 참조할 수 없다.
  • 지네릭 타입의 배열을 (선언은 가능하지만) 생성하는 것은 불가능하다.
    • new연산자, instacneof연산자는 컴파일 시점에 타입T가 뭔지 정확하게 알아야 하기 때문에 T를 피연산자로 사용할 수 없다.
/* 지네릭스는 인스턴스별로 다르게 동작하려고 만든 기능 */
Box<Apple> appleBox = new Box<Apple>(); // OK: Apple객체만 저장가능
Box<Grape> grapeBox = new Box<Grape>(); // OK: Grape객체만 저장가능

/* 타입변수는 static에는 사용불가 */
class Box<T> {
  static T item; // ERROR
  static int compare(T t1, T t2) { ... } // ERROR
}

/* 지네릭타입배열 생성불가 */
class Box<T> {
  T[] itemArr; // OK: T타입의 배열을 위한 참조변수
  ...
  T[] toArray() {
    T[] tmpArr = new T[itemArr.length]; // ERROR: 지네릭 배열 생성 불가
    ...
    return tmpArr;
  }
}

참고: 지네릭 배열을 꼭 생성해야 할 경우 방법

  1. new연산자 대신 'Reflection API'의 newInstance()와 같이 동적으로 객체를 생성하는 메서드로 배열 생성
  2. Object배열을 생성해서 복사한 다음에 T[]로 형변환

1.3 지네릭 클래스의 객체 생성과 사용

Box<T> 지네릭 클래스 정의

class Box<T> {
  ArrayList<T> list = new ArrayList<T>();

  void add(T item) { list.add(item); }
  T get(int i) { return list.get(i); }
  ArrayList<T> getList() { return list; }
  int size() { return list.size(); }
  public String toString() { return list.toString(); }
}

Box<T> 객체 생성

Box<Apple> appleBox = new Box<Apple>(); // OK
// ERROR: 참조변수와 생성자에 대입된 타입이 일치해야 한다.
Box<Apple> appleBox = new Box<Grape>(); 

// 타입 상속관계: Apple이 Fruit의 자손일 경우
// ERROR: 상속관계여도 일치된 타입이여야 한다.
Box<Fruit> appleBox = new Box<Apple>(); 

// 지네릭 클래스 상속관계: FruitBox<T>가 Box<T>의 자손인 경우
Box<Apple> appleBox = new FruitBox<Apple>(); // OK: 다형성

// 추정가능한 타입 생략 가능 (JDK1.7부터)
Box<Apple> appleBox = new Box<Apple>(); // OK
Box<Apple> appleBox = new Box<>(); // JDK1.7부터 OK

Box<T>.add() 사용

// 생성시 대입된 타입과 다른 타입의 객체 추가 불가
Box<Apple> appleBox = new Box<Apple>();
appleBox.add(new Apple()); // OK
appleBox.add(new Grape()); // ERROR

// 타입 상속관계: Apple이 Fruit의 자손
Box<Fruit> fruitBox = new Box<Fruit>();
fruitBox.add(new Fruit()); // OK
fruitBox.add(new Apple()); // ERROR: Fruit만 가능

1.4 제한된 지네릭 클래스

FruitBox<Toy> fruitBox = new FruitBox<Toy>();
fruitBox.add(new Toy()); // OK

// Fruit의 자손만 타입으로 지정
class FruitBox<T extends Fruit> {
    ArrayList<T> list = new ArrayList<T>();
    ...
}

class Apple extends Fruit { ... }
class Grape extends Fruit { ... }
class Toy { ... }

FruitBox<Apple> appleBox = new FruitBox<Apple>(); // OK: Apple은 Fruit의 자손
FruitBox<Toy> toyBox = new FruitBox<Toy>(); // ERROR: Toy는 Fruit의 자손이 아님

// 다형성: 매개변수의 타입 T도 Fruit과 그 자손타입이 가능해짐
// (따라서, T에 Object를 대입하면 모든 종류의 객체를 저장할 수 있게 됨)
FruitBox<Fruit> fruitBox = new FruitBox<Fruit>();
fruitBox.add(new Apple()); // OK: Apple이 Fruit의 자손
fruitBox.add(new Grape()); // OK: Grape가 Fruit의 자손

// 특정 인터페이스를 구현해야 한다는 제약 (인터페이스도 implements가 아니라 extends)
interface Eatable { }
class FruitBox<T extends Eatable> { ... }

// Fruit의 자손이면서 Eatable 인터페이스도 구현해야 한다는 제약
class FruitBox<T extends Fruit & Eatable> { ... }

1.5 와일드 카드

static 메서드에는 타입 매개변수 T를 매개변수에 사용할 수 없다.

  • 지네릭스를 사용하지 않거나, 특정 타입을 지정해서 써야 한다.
    • 하지만, 특정타입을 지정하면 여러가지 타입을 받을 수가 없다.
class Juicer {
  static Juice makeJuice(FruitBox<Fruit> box) {
    String tmp = "";
    for(Fruit f: box.getList()) tmp += f + " ";
    return new Juice(tmp);
  }
}

FruitBox<Fruit> fruitBox = new FruitBox<Fruit>();
FruitBox<Apple> appleBox = new FruitBox<Apple>();

Juicer.makeJuice(fruitBox); // OK
Juicer.makeJuice(appleBox); // ERROR: FruitBox<Apple> -> FruitBox<Fruit> 형변환 안됨
  • 특정타입을 고정하면 여러가지 타입의 매개변수를 갖도록 오버로딩해야 한다.
    • 하지만, 지네릭 타입이 다른 것만으로는 오버로딩이 성립되지 않는다.
    • 이때 사용하기 위해 고안된 것이 '와일드카드'
class Juicer {
  static Juice makeJuice(FruitBox<Fruit> box) {
    String tmp = "";
    for(Fruit f: box.getList()) tmp += f + " ";
    return new Juice(tmp);
  }

  static Juice makeJuice(FruitBox<Apple> box) {
    String tmp = "";
    for(Fruit f: box.getList()) tmp += f + " ";
    return new Juice(tmp);
  }
}

와일드카드

와일드카드 기호 ?

  • 와일드카드는 어떠한 타입도 될 수 있다.
  • ?Object와 다름없으므로 extends, super로 상한과 하한을 제한 할 수 있다.
    • <? extends T>: 와일드카드의 상한 제한 (T와 그 자손들만 가능)
    • <? super T>: 와일드카드의 하한 제한 (T와 그 조상들만 가능)
    • <?>: 제한없음 (모든 타입 가능), <? extends Object>와 동일
  • 지네릭 클래스와 달리 와일드 카드에는 &을 사용할 수 없다.
    • 즉, <? extends T & E>와 같이 쓸수없음
class Juicer {
  static Juice makeJuice(FruitBox<? extends Fruit> box) {
    String tmp = "";
    for(Fruit f: box.getList()) tmp += f + " ";
    return new Juice(tmp);
  }
}

<? extends Object>

  • 모든타입이 가능
class Juicer {
  static Juice makeJuice(FruitBox<? extends Object> box) {
    String tmp = "";
    for(Fruit f: box.getList()) tmp += f + " ";
    return new Juice(tmp);
  }
}
  • makeJuice()의 매개변수로 모든 타입의 FruitBox가 올 수 있지만, for문에서는 Fruit만을 받기때문에 Fruit타입만을 받을 수 있다.
  • 그러나, FruitBox는 지네릭클래스로 Fruit을 제한하고 있기 때문에 컴파일에 문제가 생기지 않는다.
    • 컴파일러는 FruitBox의 요소가 모두 Fruit의 자손이라는 것을 알기 때문
class FruitBox<T extends Fruit> extends Box<T> { }

1.6 지네릭 메서드

메서드의 선언부에 지네릭 타입이 선언된 메서드

// Collections.sort()
static <T> void sort(List<T> list, Comparator<? super T> c)
  • 지네릭 클래스의 타입매개변수 T와 지네릭 메서드의 타입매개변수 T는 별개의 것
    • 서로 같은문자를 쓰더라도
  • 지네릭 메서드는 지네릭 클래스가 아닌 클래스에도 정의 가능
  • static 멤버에는 타입 매개변수를 사용할 수 없지만, static 메서드에는 가능
  • 메서드에 선언된 타입은 지역변수와 같은 느낌
    • 메서드 내에서만 사용되므로 static이건 아니건 상관없음

지네릭 메서드로 바꾸기

// static Juice makeJuice(FruitBox<? extends Fruit> box) {
static <T extends Fruit> Juice makeJuice(FruitBox<T> box) {
  String tmp = "";
  for(Fruit f: box.getList()) tmp += f + " ";
  return new Juice(tmp);
}

FruitBox<Fruit> fruitBox = new FruitBox<Fruit>();
FruitBox<Apple> appleBox = new FruitBox<Apple>();

Juicer.<Fruit>makeJuice(fruitBox);
Juicer.makeJuice(fruitBox); // 타입 생략가능 (컴파일러가 추정가능)

Juicer.<Apple>makeJuice(appleBox); 
Juicer.makeJuice(appleBox); // 타입 생략가능 (컴파일러가 추정가능)

주의: 지네릭 메서드 호출시 대입된 타입 생략이 불가능한 경우에는 참조변수나 클래스 이름을 생략할 수 없다.

<Fruit>makeJuice(fruitBox); // ERROR: 클래스 이름 생략불가
this.<Fruit>makeJuice(fruitBox); // OK
Juicer.<Fruit>makeJuice(fruitBox); // OK

매개변수의 타입이 복잡할 때 간략화

// public static void printAll(ArrayList<? extends Product> list, ArrayList<? extends Product> list2) {
public static <T extends Product> void printAll(ArrayList<T> list, ArrayList<T> list2) {
  for(Unit u: list) { System.out.println(u); }
}

복잡하게 선언된 지네릭 메서드 예제

// Collections.sort()
public static <T extends Comparable<? super T>> void sort(List<T> list)
  • List<T>: 타입 T를 요소로 하는 List를 매개변수로 허용한다.
  • <T extends Comparable>: TComparable을 구현한 클래스여야 한다.
  • Comparable<? super T>: List<T>의 요소가 T 또는 그 조상 타입을 비교하는 Comparable 인터페이스를 구현한 것이어야 한다.
  • <? super T>: TStudent이고 Person의 자손이라면, <? super T>Student, Person, Object가 모두 가능하다.

1.7 지네릭 타입의 형변환

지네릭타입 <-> 원시타입: OK

Box box = null;
Box<Object> objBox = null;

box = (Box)objBox; // OK: Box<Object> -> Box (경고)
objBox = (Box<Object>)box; // OK: Box -> Box<Object> (경고)

지네릭타입 <-> 지네릭타입: ERROR

Box<Object> objBox = null;
Box<String> strBox = null;

// ERROR: Box<String> -> Box<Object>
objBox = (Box<Object>)strBox; 
// ERROR: Box<Object> -> Box<String>
strBox = (Box<String>)objBox; 

// ERROR: Box<String> -> Box<Object>
Box<Object> objBox2 = new Box<String>();

와일드카드 지네릭타입 <-> 지네릭타입: OK

// OK: Box<String> -> Box<? extends Object>
// 매개변수의 다형성 제공
Box<? extends Object> wBox = new Box<String>(); 

FruitBox<? extends Fruit> box = null;
// OK: FruitBox<? extends Fruit> -> FruitBox<Apple>
// 미확인 타입으로 형변환 경고
FruitBox<Apple> appleBox = (FruitBox<Apple>)box;

java.util.Optional클래스

public final class Optional<T> {
  private static final Optional<?> EMPTY = new Optional<>();

  private final T value;
  ...
  public static <T> Optional<T> empty() {
    Optional<T> t = (Optional<T>) EMPTY;
    return t;
  }
}
  • <?>: <? extends Object>를 줄여서 쓴 것
  • <>: Object를 줄여서 쓴것
  • EMPTY타입을 Optional<?>로 선언한 이유
    • empty()에서 Optioanl<T>로 형변환이 가능하기 때문
    • Optional<Object> -> Optional<T>: 형변환 불가능
    • Optional<T> -> Optional<?> -> Optional<T>: 형변환 가능 (경고)

와일드카드 지네릭 타입 <-> 와일드카드 지네릭타입: OK

FruitBox<? extends Object> objBox = null;
FruitBox<? extends String> strBox = null;

// OK: FruitBox<? extends Object> -> FruitBox<? extends String>
// 미확정 타입으로 형변환 경고
strBox = (FruitBox<? extends String>)objBox; 

// OK: FruitBox<? extends String> -> FruitBox<? extends Object>
// 미확정 타입으로 형변환 경고
objBox = (FruitBox<? extends Object)strBox;

1.8 지네릭 타입의 제거

컴파일러는 지네릭 타입을 이용해서 소스 파일을 체크하고, 필요한 곳에 형변환을 넣어준 뒤 지네릭 타입을 제거한다.

  • 즉, 컴파일된 파일(*.class)에는 지네릭 타입에 대한 정보가 없다.
  • 이전의 소스 코드와의 호환성을 유지하기 위한 것이다.
  • (언젠가는 하위호환성을 포기하게 될 것이므로) 가능하면 원시타입을 사용하지 않도록 한다.

기본적인 지네릭 타입 제거 과정

  • 1단계: 지네릭 타입의 경계(bound)를 제거
// class Box<T extends Fruit> {
class Box {
  // void add(T t) {
  void add(Fruit t) {
    ...
  }
}
  • 2단계: 지네릭 타입을 제거한 후에 타입이 일치하지 않으면 형변환 추가
// T get(int i) {
Fruit get(int i) {
  // return list.get(i);
  return (Fruit)list.get(i);
}

// static Juice makeJuice(FruitBox<? extends Fruit> box) {
static Juice makeJuice(FruitBox box) {
  String tmp = "";
  // for(Fruit f: box.getList()) tmp += f + " ";
  Iterator it = box.getList().iterator();
  while(it.hasNext()) {
    tmp += (Fruit)it.next() + " ";
  }
  return new Juice(tmp);
}

2. 열거형(enums)

2.1 열거형이란?

  • JDK 1.5 새로 추가.
  • C언어의 열거형보다 향상된 기능 : 값 뿐만 아니라 타입까지 관리 (논리적인 오류를 줄임)
public class Card {
    enum Kind { CLOVER, HEART, DIAMOND, SPADE }
    enum Value {TWO, THREE, FOUR }

    final Kind kind = Kind.CLOVER;
    final Value value = Value.TWO;
}
  • 상수 사용할 경우, 상수를 사용하면 해당 상수를 참조하는 모든 소스를 다시 컴파일 해야함
  • 열거형 상수를 사용하면 기존 소스를 다시 컴파일 하지 않아도 된다.

2.2 열거형의 정의와 사용

열거형 정의

  // enum 열거형 이름 { 상수명1, 상수명2, ...} 
  enum Direction { EAST, WEST, SOUTH, NORTH }

열거형 사용

  • 사용 : 열거형 이름.상수명
  • 비교 연산 :
    • "==" 사용 (equals(o))
    • "compareTo()" 사용 (">", "<" 사용 불가) : 왼쪽이 크면 양수, 오른쪽이 크면 음수
  // 열거형 이름.상수명
  Direction direction = Direction.EAST;

  // 비교 연산
  if( direction == Direction.EAST )
    System.out.println("Test 1");
  else if (direction.compareTo(Direction.WEST) > 0)
    System.out.println("Test 2");
  // 에러!! (비교연산자 사용 불가)
  //else if (direction > Direction.WEST) 
  //  System.out.println("Test 3");
  • switch 조건식 : 열거형 사용

    • case문은 열거형 이름은 적지 않고 상수이름만 적어야 함
    • case Direction.EAST: // Error!!
      void move(Direction direction) {
          switch (direction) {
          case EAST:
              x++;
              break;
          case WEST:
              y++;
              break;
          case SOUTH:
              x--;
              break;
          case NORTH:
              y--;
              break;
          }
      }
      
  • 모든 열거형의 조상 - java.lang.Enum

    • 다음과 같은 메서드가 정의되어 있음
      Class<E> getDeclaringClass() // 열거형의 클래스 객체를 반환
      String name() // 열거형 상수의 이름을 문자열로 반환
      int ordinal() // 열거형 상수가 정의된 순서를 반환 (0부터 시작)
      T valueOf(Class<T> enumType, String name) // 지정된 열거형에서 name 과 일치하는 열거형 상수를 반환
      
       Direction direction = Enum.ValueOf(Direction.class, "EAST");
       String name = direction.name();
      
    • 열거형에 모든 상수를 출력하려면..
       Direction[] directionArr = Direction.values();
       for(Direction direction : directionArr) {
         System.out.println("%s=%d\n", direction.name(), direction.ordinal());
       }
      

2.3 열거형에 멤버 추가하기

  • 열거형 상수에 지정한 값을 넣기 -> 인스턴스 변수와 생성자를 새로 추가해주어야 한다.

    public enum Table {
      App("tbl_app"), 
      AppInfo("tbl_app_info"), 
      GameCompany("tbl_game_company");
    
      // 인스턴스 변수에 반드시 final 을 붙여야한다는 제약은 없지만,
      // value는 열거형 상수값을 저장하기 위한 것이므로 final을 붙였음.
      private final String value;
    
      private Table(String value) {
          this.value = value;
      }
    
      public String getValue() { return tableName; }
    }
    
  • 열거형에 추상 메서드 추가하기

    public enum Transportation {
      BUS(100) {
          @Override
          int fare(int distance) {
              return distance * BASIC_FARE;
          }
      },
      TRAIN(150) {
          int fare(int distance) {
              return distance * BASIC_FARE;
          }
      };
    
      /***********************************
       * 거리에 따른 요금 계산하 추상 메서드
       ***********************************/
      abstract int fare(int distance);
    
      // protected로 해야 각 열거형 상수에서 접근 가능
      protected final int BASIC_FARE;
      private Transportation(int basicFare) {
          BASIC_FARE = basicFare;
      }
    
      int fare() {
          return BASIC_FARE;
      }
    }
    

2.4 열거형의 이해

  • 기본 Enum 과 유사한 MyEnum 을 만들어 보는 예제
public abstract class MyEnum<T> implements Comparable<T> {

    static int id = 0;
    int ordinal;
    String name;

    public int ordinal() { return ordinal; }
    MyEnum(String name) {
        this.name = name;
        ordinal = id++; // 객체를 생성할 때마다 1씩 증가시킨다.
    }

    public int compareTo(T t) {
        return ordinal - t.ordinal(); // 에러. 타입 T에 ordinal 사용불가
    }
}

// compareTo 에러 수정 
public abstract class MyEnum<T extends MyEnum<T>> implements Comparable<T> {

    //...

    public int compareTo(T t) {
        return ordinal - t.ordinal(); // 에러. 타입 T에 ordinal 사용 가능
    }
}
MyEnum<T extends MyEnum<T>>
// 타입 T가 MyEnum<T> 의 자손이어야 한다는 의미
// T가 MyEnum의 자손이므로 t.ordinal() 접근이 가능해짐
abstract class MyEnum<T extends MyEnum<T>> implements Comparable<T> {

    //...
}

abstract class MyTransportation extends MyEnum {
  static final MyTransportation BUS = new MyTransportation("BUS", 100);
  static final MyTransportation TRAIN = new MyTransportation("TRAIN", 150);

  protected final int BASIC_FARE;
  private MyTransportation(String name, int basicFare) {
    super(name);   // ordinal 값 접근 가능
    BASIC_FARE = basicFare;
  }

  public String name() { return name; }
  public String toString() { return name; }
}

class EnumEX {
  public static void main(String[] args) {
    MyTransportation t1 = MyTransportation.BUS;
    MyTransportation t2 = MyTransportation.TRAIN;

    ...
  }
}

3. 애너테이션(annotation)

3.1 애너테이션이란?

자바를 개발하는 사람들은 소스코드에 대한 문서를 따로 만들기보다는 문서를 하나의 파일로 관리 하는 것이 낫다고 생각하였다. -> javadoc

/** 
* 자바 API문서 만들기 예제 소스 
*  
* @author 별소리
* @version 1.0 
*   
*/
public class JavaDocTest 
{
  ...
}

애너테이션

  • 소스코드 안에 다른 프로그램을 위한 정보를 미리 약속된 형식으로 포함 시킨 것
  • 주석(comment)처럼 프로그래밍 언어에 영향을 미치지 않으면서 다른 프로그램에게 유용한 정보 제공 가능
  @Test // 이 메서드가 테스트 대상임을 테스트 프로그램에게 알린다.
  public void method() {
    ...
  }

애너테이션의 종류

  • @Test 와 같이 JDK가 기본적으로 제공해주는 것 : 표준 애너테이션
  • 다른 프로그램에서 제공하는 것들이 있음

3.2 표준 애너테이션

애너테이션 설명
@Override 컴파일러에게 오버라이딩하는 매서드라는 것을 알린다
@Deprecated 앞으로 사용하지 않을 것으로 권장하는 대상에 붙인다
@SuppressWarnings 컴파일러의 특정 경고메세지가 나타나지 않게 해준다
@SafeWarargs 지네릭스 타입의 가변인자에 사용한다 (JDK1.7)
@FunctionalInterface 함수형 인터페이스라는 것을 알린다 (JDK1.8)
@Native native 메서드에서 참조되는 상수 앞에 붙인다 (JDK1.8)
@Target 애너테이션 적용가능한 대상을 지정하는데 사용한다
@Documented 애니테이션 정보가 javadoc으로 작성된 문서에 포함되게 한다
@Inherited 애니테이션 자손 클래스에 상속되도록 한다
@Retention 애너테이션이 유지되는 범위를 지정하는데 사용한다
@Repeatable 애너테이션을 반복해서 적용할 수 있게 한다 (JDK1.8)
  • Override 애너테이션

    class Child extends Parent {
      @Override
      void parentMethod() {
        ...
      }
    }
    
    • @Override 애너테이션을 붙여주면, 컴파일러가 같은 이름의 메서드가 조상에 있는지 확인하고, 없으면 에러 메시지를 출력해준다
  • Deprecated 애너테이션 : JDK1.1부터 추가된 Calendar 클래스의 get 을 사용하도록 유도

    class Calendar {
      @Deprecated
      int getDate() {
        ...
      }
    }
    
  • SuppressWarnings : deprecation, unchecked, unused 경고 메세지가 나타나지 않게 해준다

      public void start() {
          @SuppressWarnings("unused")
          int test = 0;
      }
    

3.3 메타 애너테이션

메타 애너테이션은 '애너테이션을 위한 애너테이션', 즉 애너테이션에 붙이는 애너테이션으로 애너테이션의 적용 대상(target)이나 유지기간(retention)등을 지정하는데 사용된다

import java.lang.annotation.Target;
import static java.lang.annotation.ElementType.*;

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;

@Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE})
@Retention(RetentionPolicy.SOURCE)
public @interface SuppressWarnings {

    String[] value();
}

[코드: SuppressWarnings 애너테이션 정의 예제]

  • @Target : 적용가능한 대상을 지정하는데 사용된다.
대상 타입 의미
ANNOTATION_TYPE 애너테이션
CONSTRUCTOR 생성자
FIELD 필드(멤버변수, enum상수)
LOCAL_VALIABLE 지역변수
METHOD 매서드
PACKAGE 패키지
PARAMETER 매개변수
TYPE 타입(클래스, 인터페이스, enum, 애너테이션)
TYPE_PARAMETER 타입 매개변수(JDK1.8)
TYPE_USE 타입에 사용되는 모든 곳(JDK1.8)
  • @Retention : 유지(retention)되는 기간
유지 정책 의미
SOURCE 소스 파일에만 존재, 클래스 파일에는 존재하지 않음
CLASS 클래스 파일에 존재, 실행시에 사용 불가. 기본값
RUNTIME 클래스 파일 존재, 실행시에 사용 가능
  • @Documendted : javadoc으로 작성한 문서에 포함되도록 한다
  • @Inherited : 자손 클래스에 상속되도록 한다. 조상 클래스에 붙이면, 자손 클래스도 애너테이션이 붙는 것과 같이 인식된다
  • @Repeatable : 보통 하나의 대상에 한종류의 애너테이션을 붙일 수 있음. 여러번 붙일 수 있도록 하는 메타 애너테이션
  • @Native : 네이티브 메서드에 의해 참조되는 '상수필드(constanct field)'에 붙이는 애너테이션

3.4 애너테이션 타입 정의하기

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

  @interface 애너테이션이름 {
    타입 요소이름(); // 애너테이션의 요소를 선언한다
    ...
  }
  • 애너테이션 요소는 반환값이 있음
  • 매개변수는 없는 추상 메서드의 형태를 가짐 : 상속을 통해 구현하지 않아도 됨
  • 애너테이션이 적용될 때는 요소들의 값을 빠짐없이 지정해주어야 한다
  • 요소의 이름도 같이 적어주므로, 순서는 상관없다

애너테이션 정의 예제

  @interface TestInfo {
    int count() default 1;
    String testBy();
    String[] testTools();
    TestType testType(); // enum TestType {FIRST, FINAL}
    DateTime testDate(); // 자신이 아닌 다른 애너테이션(@Datatime)을 토함시킬수 있다

  }

  @interface DateTime {
    String yymmdd();
    String hhmmss();
  }

애너테이션 사용 예제

  @TestInfo {
    count=3, testBy="Kim",
    testTools={"JUnit","AutoTester"},
    testType=TestType.FIRST,
    testDate=@DateTime(yymmdd="160101", hhmmss="235959")
  }
  public class TestClass { ... }

애너테이션 요소의 규칙

  • 요소의 타입으로 기본형은 String, enum, 애너테이션, Class만 허용된다
  • () 안에 매개변수를 선언할 수 없다
  • 예외를 선언할 수 없다
  • 요소를 타입 매개변수로 정의할 수 없다
  @interface AnnTest {
    int id = 100; // OK, 상수선언. static final int id = 100;
    String major(int i, int j); // 에러, 매개변수를 선언할 수 없음
    String minor() Throws Exception; // 에러, 예외를 선언할 수 없음
    ArrayList<T> list(); // 에러, 요소의 타입에 타입매개변수 사용 불가
  }
  @Scheduled(cron = "* * 1 * * ?") // 매일 오전 1시에 실행한다.
  public void doSomething() {
    // doSomething
  }

  @Scheduled(fixedDelay = 3600000) // 1000 * 60 * 60, 즉 1시간마다 실행한다.
  public void doSomething() {
    // doSomethingßß
  }