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;
}
}
참고: 지네릭 배열을 꼭 생성해야 할 경우 방법
- new연산자 대신 'Reflection API'의
newInstance()
와 같이 동적으로 객체를 생성하는 메서드로 배열 생성 - 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>
:T
는Comparable
을 구현한 클래스여야 한다.Comparable<? super T>
:List<T>
의 요소가T
또는 그 조상 타입을 비교하는Comparable
인터페이스를 구현한 것이어야 한다.<? super T>
:T
가Student
이고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 annotation at Springframework
@Scheduled(cron = "* * 1 * * ?") // 매일 오전 1시에 실행한다.
public void doSomething() {
// doSomething
}
@Scheduled(fixedDelay = 3600000) // 1000 * 60 * 60, 즉 1시간마다 실행한다.
public void doSomething() {
// doSomethingßß
}