해당 게시물은 자바의 정석을 정리한 내용 입니다.
1. 제네릭(Generic)
1.1 제네릭?
제네릭의 정의
- 제네릭(Generic)은 컴파일 시 타입을 체크 해 주는 기능이다.
- 또는 타입을 파라미터화 해서 컴파일 시 구체적인 타입이 결정 되도록 하는 것이다.
제네릭의 장점
- 객체의 타입 안정성을 높인다.
※ "타입 안정성을 높인다"는 것은 의도하지 않은 타입의 객체가 저장되는 것을 막고, 저장된 객체를 꺼내올 때
원래의 타입과 다른 타입으로 잘못 형변환 되어 발생할 수 있는 오류를 줄여준다는 뜻이다.
- 형 변환의 번거로움을 줄여준다.
1.2 제네릭 클래스의 선언
- 제네릭 클래스를 작성할 때, Object 타입 대신 타입 매개변수(E)를 선언해서 사용한다.
① 예를 들어, ArrayList 클래스는 다음과 같이 정의 되어 있었다.
public class ArrayList extends AbstractList{
private transient Object[] elementData;
public boolean add(Object o) { /* 내용 생략 */ }
public Object add(int index) { /* 내용 생략 */ }
...
}
② 클래스 이름 옆에 '<E>'를 붙인 다음, 'Object'가 모두 'E'로 변경되었다. (JDK 1.5 부터)
public class ArrayList<E> extends AbstractList<E>{
private transient E[] elementData;
public boolean add(E o) { /* 내용 생략 */ }
public E add(int index) { /* 내용 생략 */ }
...
}
- 제네릭 클래스의 객체를 생성할 때는 타입 매개변수(E) 대신에 실제 타입(String)을 지정해야 한다.
- 타입 매개변수 대신 실제 타입이 지정되면, 형 변환 생략 가능
// 타입 매개변수 E 대신에 실제 타입 Tv를 대입
ArrayList<Tv> tvList = new ArrayList<Tv>();
tvList.add(new Tv());
Tv t = tvList.get(0); // 형 변환 생략 가능
1.3 제네릭의 용어
Box<T> : 제네릭 클래스. ‘T의 Box’ 또는 ‘T Box’라고 읽는다.
T : 타입 변수 또는 타입 매개변수. (T는 타입 문자)
Box : 원시 타입(raw type)
아래와 같이 타입 매개변수에 타입을 지정하는 것을 "제네릭 타입 호출"이라 한다.
지정된 타입 'String'을 매개변수화된 타입이라 한다. (해당 용어가 좀 길어서, "대입된 타입"이라는 용어를 사용할 것이다.)
1.4 제네릭 타입과 다형성
- 참조 변수와 생성자의 대입된 타입은 일치 해야 한다.
ArrayList<Tv> tvList = new ArrayList<Tv>(); // OK. 일치
ArrayList<Product> productList = new ArrayList<Tv>(); // 에러. 불일치
- 제네릭 클래스 간의 다형성은 성립한다. (여전히 대입된 타입은 일치 해야 한다.)
List<Tv> list1 = new ArrayList<Tv>();
List<Tv> list2 = new LinkedList<Tv>();
- 매개변수의 다형성도 성립한다.
ArrayList<Product> list = new ArrayList<Product>();
list.add(new Product());
list.add(new Tv()); // OK
list.add(new Audio()); // OK
boolean add(E e){ ... } // E에는 Product가 대입 됨 그래서 Product와 그 자손 객체가 가능
- JDK 1.7 부터 타입 추론이 가능한 경우, 생성자의 타입을 생략 할 수 있다.
(참조변수의 타입으로 ArrayList가 Product 타입의 객체만 저장한다는 것을 알 수 있기 때문에 생략 가능하다.)
ArrayList<Product> list = new ArrayList<>();
1.5 제네릭 타입의 형 변환
- 제네릭 타입과 원시 타입 간의 형 변환은 가능 하지만 바람직 하지 않다. (경고가 발생)
Box box = null; // 원시 타입
Box<Object> objBox = null; // 제네릭 타입
box = (Box)objBox; // OK. 제네릭 타입 -> 원시 타입 (경고 발생)
objBox = (Box<Object>)box; // OK. 원시 타입 -> 제네릭 타입 (경고 발생)
- 대입된 타입이 다른 제네릭 타입 간의 형 변환은 불가능하다.
Box<Object> objBox = null;
Box<String> strBox = null;
objBox = (Box<Object>)strBox; // 에러. Box<String> -> Box<Object>
strBox = (Box<String>)objBox; // 에러. Box<Object> -> Box<String>
- 와일드 카드가 사용된 제네릭 타입으로는 형 변환이 가능하다.
① FruitBox<Apple> → FruitBox<? extends Fruit>
Box<Object> objBox = (Box<Object>) new Box<<String>(); // 에러. 형변환 불가능
Box<? extends Object> wBox = (Box<? extends Object>) new Box<String>(); // OK
Box<? extends Object> wBox2 = new Box<String>(); // 위 문장과 동일
// 매개변수로 FruitBox<Fruit>, FruitBox<Apple>, FruitBox<Grape> 등이 가능
static Juice makeJuice(FruitBox<? extends Fruit> box) { ... }
FruitBox<? extends Fruit> box = new FruitBox<Fruit>(); // OK
// FruitBox<Apple> → FruitBox<? extends Fruit>
FruitBox<? extends Fruit> box = new FruitBox<Apple>(); // OK
② FruitBox<? extends Fruit> → FruitBox<Apple>
와일드 카드가 사용된 제네릭 타입에서 제네릭 타입으로의 형변환은 "확인되지 않은 형변환"이라는 경고가 발생한다.
FruitBox<? extends Fruit>는 대입될 수 있는 타입은 여러 개인데, 명확한 타입인 FruitBox<Apple>으로 형 변환하려고
하기 때문에 경고가 발생하는 것이다.
// FruitBox<? extends Fruit> → FruitBox<Apple>
FruitBox<? extends Fruit> box = null;
FruitBox<Apple> appleBox = (FruitBox<Apple>) box; // OK. 미확인 타입으로 형 변환 경고
1.6 제네릭 타입의 제거
- 컴파일러는 제네릭 타입을 제거하고, 필요한 곳에 형 변환을 넣는다.
① 제네릭 타입의 경계(bound)를 제거한다.
② 제네릭 타입을 제거한 후에 타입이 일치하지 않으면, 형 변환을 추가한다.
③ 와일드 카드가 포함되어 있는 경우에는 적절한 타입으로의 형 변환이 추가된다.
1.7 제한된 제네릭 클래스
- 제네릭 타입에 ‘extends’를 사용하면, 특정 타입의 자손들만 대입할 수 있게 제한 할 수 있다.
<T extends 조상 타입>
class FruitBox<T extends Fruit> { // Fruit과 그 자손만 타입으로 지정 가능
ArrayList<T> list = new ArrayList<T>();
void add(T item) {list.add(item);}
...
}
- 게다가 add()의 매개변수 타입 T도 Fruit와 그 자손 타입이 될 수 있다.
그러므로 아래와 같이 여러 과일을 담을 수 있는 과일 상자가 된다.
FruitBox<Fruit> fruitBox = new FruitBox<Fruit>();
fruitBox.add(new Apple()); // OK. Apple이 Fruit의 자손
fruitBox.add(new Grape()); // OK. Grape가 Fruit의 자손
- 인터페이스를 구현해야 한다는 제약이 필요하면 ‘implements’가 아닌, ‘extends’를 사용한다.
interface Eatable {}
// Eatable 인터페이스를 구현한 클래스만 타입 매개변수 T에 대입 될 수 있다.
class FruitBox<T extends Eatable> { . . . }
// Fruit의 자손이면서 Eatable 인터페이스를 구현한 클래스만 타입 매개변수 T에 대입 될 수 있다.
class FruitBox<T extends Fruit & Eatable> { . . . }
class Fruit implements Eatable {
public String toString() { return "Fruit";}
}
class Apple extends Fruit { public String toString() { return "Apple";}}
class Grape extends Fruit { public String toString() { return "Grape";}}
class Toy { public String toString() { return "Toy" ;}}
interface Eatable {}
class Ex12_3 {
public static void main(String[] args) {
FruitBox<Fruit> fruitBox = new FruitBox<Fruit>();
FruitBox<Apple> appleBox = new FruitBox<Apple>();
FruitBox<Grape> grapeBox = new FruitBox<Grape>();
// FruitBox<Grape> grapeBox = new FruitBox<Apple>(); // 에러. 타입 불일치
// FruitBox<Toy> toyBox = new FruitBox<Toy>(); // 에러.
fruitBox.add(new Fruit());
fruitBox.add(new Apple());
fruitBox.add(new Grape());
appleBox.add(new Apple());
// appleBox.add(new Grape()); // 에러. Grape는 Apple의 자손이 아님
grapeBox.add(new Grape());
System.out.println("fruitBox-"+fruitBox);
System.out.println("appleBox-"+appleBox);
System.out.println("grapeBox-"+grapeBox);
} // main
}
class FruitBox<T extends Fruit & Eatable> extends 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); }
int size() { return list.size(); }
public String toString() { return list.toString();}
}
1.8 제네릭의 제약
- static 멤버에는 타입 매개변수(T)를 사용 할 수 없다.
→ 타입 매개변수에 대입하는 것은 인스턴스 마다 다르게 지정 할 수 있다.
그래서 static 멤버는 같은 클래스의 모든 인스턴스들이 공통으로 사용하는 멤버이기 때문이다.
class Box<T> {
static T item; // 에러
static int compare(T t1, T t2){ ... } // 에러
. . .
}
- 타입 매개변수(T)로 배열을 생성 할 수 없다. (타입 매개변수로 배열을 선언하는 것은 가능)
즉, new 연산자 뒤에 타입 매개변수(T)를 사용 할 수 없다.
class Box<T> {
T[] itemArr; // OK. T 타입의 배열을 위한 참조변수
. . .
T[] toArray(); {
T[] tmpArr = new T[itemArr.length]; // 에러. 제네릭 타입의 배열 생성 불가
. . .
return tmpArr;
}
. . .
}
- 제네릭 타입의 배열을 생성 해야 한다면 Object 배열을 만들고 T 타입의 배열로 형 변환하면 된다.
T[] tmpArr = (T[]) new Object[itemArr.length]; // Object 배열을 만들고 T 타입 배열로 형 변환 하면 된다.
1.9 와일드 카드 '?'
- 와일드 카드를 사용하면 하나의 참조 변수로 대입된 타입이 다른 객체를 참조 할 수 있다.
- 와일드카드는 기호 '?'로 표현하며 와일드 카드는 어떠한 타입도 될 수 있다.
<? extends T> : 와일드 카드의 상한 제한. T와 그 자손들만 가능
<? super T> : 와일드 카드의 하한 제한. T와 그 조상들만 가능
<?> : 제한없음. 모든 타입 가능. <? extends Object>와 동일
- 와일드 카드에는 "&"를 사용 할 수 없다.
즉, <? extends T & E>와 같이 사용 할 수 없다.
- 메서드의 매개변수에 와일드 카드를 사용 할 수 있다.
static Juice makeJuice(FruitBox<? extends Fruit> box) {
String tmp = "";
for(Fruit f: box.getList()) tmp += f + " ";
return new Juice(tmp);
}
이제 이 메서드의 매개변수로 FruitBox<Fruit> 뿐만 아니라, FruitBox<Apple>와 FruitBox<Grape>도 가능하다.
FruitBox<Fruit> fruitBox = new FruitBox<Fruit>();
FruitBox<Apple> appleBox = new FruitBox<Apple>();
. . .
System.out.println(Juicer.makeJuice(fruitBox)); // OK. FruitBox<Fruit>
System.out.println(Juicer.makeJuice(appleBox)); // OK. FruitBox<Apple>
1.10 제네릭 메서드
- 제네릭 메서드는 반환 타입 앞에 제네릭 타입이 선언된 메서드이다.
이전 Chapter에서 살펴본 Collections.sort()가 바로 제네릭 메서드이다.
static <T> void sort(List<T> list, Comparator<? super T> c)
- 제네릭 클래스의 타입 매개변수 <T>와 제네릭 메서드의 타입 매개변수 <T>는 별개
class FruitBox<T> {
. . .
static <T> void sort(List<T> list, Comparator<? super T> c) {
. . .
}
}
static 멤버에는 타입 매개변수를 사용할 수 없지만, 메서드에 제네릭 타입을 선언하고 사용하는 것은 가능하다.
- 앞서 나왔던 makeJuice()를 제네릭 메서드로 바꾸면 다음과 같다.
static <T extends Fruit> Juice makeJuice(FruitBox<T> box) { // T는 Fruit과 그 자손들만 가능
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>();
System.out.println(Juicer.<Fruit>makeJuice(fruitBox)); // 메서드를 호출 할 때 타입을 대입 해야 하나
System.out.println(Juicer.makeJuice(fruitBox)); // 대입된 타입을 생략 할 수 있다.
- 제네릭 메서드를 호출할 때, 타입을 생략 하지 않는다면 클래스 이름을 작성 해야 한다.
System.out.println(<Fruit>makeJuice(fruitBox)); // 에러. 클래스 이름 생략 불가
System.out.println(this.<Fruit>makeJuice(fruitBox)); // OK
System.out.println(Juicer.<Fruit>makeJuice(fruitBox)); // OK
※ 제네릭 메서드와 와일드 카드의 차이
- 와일드 카드는 하나의 참조변수로 서로 다른 타입이 대입된 여러 제네릭 객체를 다루기 위한 것이다.
- 제네릭 메서드는 메서드를 호출 할 때 마다 다른 제네릭 타입을 대입 할 수 있도록 한 것이다.
2. 열거형(enums)
2.1 열거형이란?
열거형의 정의
- 열거형(enum)은 관련된 상수들을 같이 묶어 놓은 것이다.
- Java는 타입에 안전한 열거형을 제공한다.
* "타입에 안전한 열거형"이라는 것은 실제 값이 같아도 타입이 다르면 컴파일 에러가 발생하는 것을 의미한다.
즉, 자바의 열거형은 값과 타입을 모두 체크한다.
2.2 열거형의 정의와 사용
1) 열거형을 정의하는 방법
enum 열거형이름 { 상수명 1, 상수명2, . . . }
enum Direction { EAST, WEST, SOUTH, NORTH }
→ Direction이라는 열거형을 선언한다. 상수의 값은 자동으로 0 부터 시작하는 정수 값이 부여된다.
2) 열거형 타입의 변수를 선언하고 사용하는 방법
enum Direction { EAST, WEST, SOUTH, NORTH }
class Unit{
int x, y; // 유닛의 초기화
Direction dir; // 열거형을 인스턴스 변수로 선언
void init() {
dir = Direction.EAST; // 유닛의 방향을 EAST로 초기화
// 열거형 변수의 값은 열거형 상수 값(EAST, WEST ..) 중 하나 이어야 한다.
}
}
3) 열거형 상수의 비교에 ==와 compareTo()를 사용 할 수 있다.
- 열거형을 비교할 때는 compareTo()를 사용 해야 한다.
- compareTo()는 열거형 상수가 정의된 순서의 차이를 반환한다.
if( dir == Direction.EAST ){
x++;
} else if (dir > Direction.WEST){ // 에러. 열거형 상수에 비교 연산자 사용 불가
...
} else if (dir.compareTo(Direction.WEST) > 0){ // compareTo()는 가능
}
2.3 모든 열거형의 조상 - java.lang.Enum
모든 열거형은 추상 클래스 Enum의 자손이며 아래의 메서드를 상속 받는다.
메서드 | 설 명 |
Class<E> getDeclaringClass() | 열거형의 Class 객체를 반환한다. |
String name() | 열거형 상수의 이름을 문자열로 반환한다. |
int ordinal() | 열거형 상수가 정의된 순서를 반환한다. (0 부터 시작) |
T valueOf(Class<T> enumType, String name) | 지정된 열거형에서 name과 일치하는 열거형 상수를 반환한다. |
아래와 같이 컴파일러가 자동적으로 추가해주는 메서드가 있다.
static E values() : 해당 열거형의 모든 상수를 배열에 담아 반환한다.
static E valueOf(String name) : 전달된 문자열과 일치하는 해당 열거형의 상수를 반환한다.
enum Direction { EAST, SOUTH, WEST, NORTH }
class EnumEx1 {
public static void main(String[] args) {
Direction d1 = Direction.EAST; // 열거형타입.상수이름
Direction d2 = Direction.valueOf("WEST");
Direction d3 = Enum.valueOf(Direction.class, "EAST");
System.out.println("d1="+d1);
System.out.println("d2="+d2);
System.out.println("d3="+d3);
System.out.println("d1==d2 ? "+ (d1==d2));
System.out.println("d1==d3 ? "+ (d1==d3));
System.out.println("d1.equals(d3) ? "+ d1.equals(d3));
// System.out.println("d2 > d3 ? "+ (d1 > d3)); // 에러
System.out.println("d1.compareTo(d3) ? "+ (d1.compareTo(d3)));
System.out.println("d1.compareTo(d2) ? "+ (d1.compareTo(d2)));
switch(d1) {
case EAST: // switch문의 case에는 Direction.EAST 라고 쓸 수 없다.
System.out.println("The direction is EAST."); break;
case SOUTH:
System.out.println("The direction is SOUTH."); break;
case WEST:
System.out.println("The direction is WEST."); break;
case NORTH:
System.out.println("The direction is NORTH."); break;
default:
System.out.println("Invalid direction."); break;
}
Direction[] dArr = Direction.values(); // 열거형의 모든 상수를 배열로 반환
for(Direction d : dArr) // for(Direction d : Direction.values())
System.out.printf("%s=%d%n", d.name(), d.ordinal());
}
}
2.4 열거형에 멤버 추가하기
- 불연속적인 열거형 상수의 경우, 원하는 값을 괄호() 안에 적는다.
enum Direction { EAST(1), SOUTH(5), WEST(-1), NORTH(10) }
- 괄호()를 사용하려면, 인스턴스 변수와 생성자를 새로 추가해 줘야 한다.
enum Direction {
EAST(1), SOUTH(5), WEST(-1), NORTH(10); // 끝에 ';'를 추가해야 한다.
private final int value; // 정수를 저장할 필드(인스턴스 변수)를 추가
Direction(int value) { this.value = value; } // 생성자를 추가
public int getValue() { return value; }
}
- 열거형의 생성자는 묵시적으로 private 이므로, 외부에서 열거형의 객체를 생성 할 수 없다.
Direction d = new Direction(1); // 에러. 열거형의 생성자는 외부에서 호출 불가
- 열거형에 멤버를 추가하는 예시
enum Direction {
EAST(1, ">"), SOUTH(2, "V"), WEST(3, "<"), NORTH(4, "^"); // 끝에 ';'를 추가해야 한다.
private final int value;
private final String symbol;
Direction(int value, String symbol) { // 접근 제어자 private이 생략됨
this.value = value;
this.symbol = symbol;
}
public int getValue() { return value; }
public String getSymbol() { return symbol; }
}
- 열거형에 추상 메서드 추가 하기
자세한 내용은 자바의 정석 p697~698를 참고하자.
2.5 열거형의 이해
열거형 Direction이 아래와 같이 선언되어 있을 때, 사실은 열거형 상수 하나 하나가 객체이다.
enum Direction { EAST, SOUTH, WEST, NORTH }
열거형 Direction은 아래와 같은 클래스로 선언된 것과 유사하다.
class Direction {
static final Direction EAST = new Direction("EAST");
static final Direction SOUTH = new Direction("SOUTH");
static final Direction WEST = new Direction("WEST");
static final Direction NORTH = new Direction("NORTH");
private String name;
private Direction(String name){
this.name = name;
}
}
3. 애노테이션(annotation)
3.1 애노테이션이란?
애노테이션?
- 애노테이션은 소스코드에 붙여서 특별한 의미를 부여하는 기능이다.
- 주석(comment)처럼 프로그래밍 언어에 영향을 미치지 않으면서도 다른 프로그램에게 유용한 정보를 제공할 수 있다.
애노테이션의 사용 예시
- @Test는 이 메서드를 테스트 해야 한다는 것을 테스트 프로그램에게 알리는 역할을 할 뿐, 메서드가 포함된 프로그램 자체에는
아무런 영향을 미치지 않는다.
@Test // 이 메서드가 테스트 대상임을 테스트 프로그램에게 알린다.
public void method() {
. . .
}
3.2 표준 애노테이션
표준 애노테이션?
- 표준 애노테이션은 Java에서 제공하는 애노테이션이다.
애노테이션 | 설 명 |
@Override | 컴파일러에게 오버라이딩하는 메서드라는 것을 알린다. |
@Deprecated | 앞으로 사용하지 않을 것을 권장하는 대상에 붙인다. |
@SuppressWarnings | 컴파일러의 특정 경고 메시지가 나타나지 않게 해준다. |
@SafeVarargs | 제네릭 타입의 가변인자에 사용한다. (JDK 1.7) |
@FunctionalInterface | 함수형 인터페이스라는 것을 알린다. (JDK 1.8) |
@Native | native 메서드에서 참조되는 상수 앞에 붙인다. |
@Target | 애노테이션이 적용 가능한 대상을 지정하는데 사용한다. |
@Documented | 애노테이션 정보가 javadoc으로 작성된 문서에 포함되게 한다. |
@Inherited | 애노테이션이 자손 클래스에 상속 되도록 한다. |
@Retention | 애노테이션이 유지되는 범위를 지정하는데 사용한다. |
@Repeatable | 애노테이션을 반복해서 적용할 수 있게 한다. (JDK 1.8) |
위의 표에서 초록 바탕의 애노테이션은 "메타 애노테이션"이다.
* 메타 애노테이션 : 애노테이션을 정의 하는데 사용되는 애노테이션이다.
@Override
- @Override는 오버라이딩을 올바르게 했는지 컴파일러가 체크하게 한다.
- 오버라이딩 할 때, 메서드 이름을 잘못 적는 실수를 하는 경우가 많다.
class Parent {
void parentMethod() {}
}
class Child extends Parent {
void parentmethod() {} // 오버라이딩 하려 했으나 실수로 이름을 잘못 적음
}
- 오버라이딩할 때는 메서드 선언부 앞에 @Override를 붙이자.
class Parent {
void parentMethod() { }
}
class Child extends Parent {
@Override
void parentmethod() { } // 조상 메서드의 이름을 잘못적었음.
}
@Deprecated
- @Deprecated는 앞으로 사용하지 않을 것을 권장하는 대상에 붙인다.
- @Deprecated의 예시로는 Date 클래스의 getDate()가 있다.
- @Deprecated가 붙은 대상을 사용하는 코드를 작성하고 컴파일하면 나타나는 메시지는 아래와 같다.
@FuntionalInterface
- @FuntionalInterface는 함수형 인터페이스를 올바르게 작성했는지 컴파일러가 체크하게 한다.
(함수형 인터페이스는 하나의 추상 메서드만 가져야 한다는 제약이 있음)
@FunctionalInterface
public interface Runnable{
public abstract void run(); // 추상 메서드
}
@SuppressWarnings
- @SuppressWarnings는 컴파일러의 경고 메시지가 나타나지 않게 억제한다.
- @SuppressWarnings로 억제 할 수 있는 경고 메시지 중 자주 사용되는 것은 아래와 같다.
① deprecation : @Deprecated가 붙은 대상을 사용해서 발생하는 경고를 억제할 때 사용한다.
② unchecked : 제네릭으로 타입을 지정하지 않았을 때 발생하는 경고를 억제할 때 사용한다.
③ rawtypes : 제네릭을 사용하지 않아서 발생하는 경고를 억제할 때 사용한다.
④ varargs : 가변 인자의 타입이 제네릭 타입일 때 발생하는 경고를 억제할 때 사용한다.
- 괄호() 안에 억제 하고자 하는 경고 메시지의 종류를 문자열로 지정한다.
@SuppressWarnings("unchecked") // 제네릭과 관련된 경고를 억제
ArrayList list = new ArrayList(); // 제네릭 타입을 지정하지 않았음
list.add(obj);
- 둘 이상의 경고를 동시에 억제하려면 다음과 같이 한다.
@SuppressWarnings({"deprecation", "unchecked", "varargs"})
- '-Xlint' 옵션으로 컴파일 하면, 경고 메시지를 확인 할 수 있다.
경고의 내용 중에서 대괄호 [] 안에 있는 것이 경고 메시지의 종류이다.
3.3 메타 애노테이션
메타 애노테이션?
- 메타 애노테이션은 애노테이션을 정의할 때 사용하는 애노테이션이다.
- 애노테이션을 정의할 때, 애노테이션의 적용 대상(target)이나 유지 기간(retention) 등을 지정하는데 사용된다.
@Target
- @Target은 애노테이션이 적용 가능한 대상을 지정하는데 사용된다.
- 여러 개의 값을 지정할 때는 배열에서 처럼 괄호 {}를 사용해야 한다.
@Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE})
@Retention(RetentionPolicy.SOURCE)
public @interface SuppressWarnings {
String[] value();
}
- @Target으로 지정 할 수 있는 애노테이션 적용 대상의 종류는 다음과 같다.
대상 타입 | 의미 |
ANNOTATION_TYPE | 애노테이션 |
CONSTRUCTOR | 생성자 |
FIELD | 필드(멤버 변수, enum 상수) |
LOCAL_VARIABLE | 지역변수 |
METHOD | 메서드 |
PACKAGE | 패키지 |
PARAMETER | 매개변수 |
TYPE | 타입 (클래스, 인터페이스, enum) |
TYPE_PARAMETER | 타입 매개변수(JDK 1.8) |
TYPE_USE | 타입이 사용되는 모든 곳 (JDK 1.8) |
* ANNOTATION_TYPE은 애노테이션을 선언할 때 붙일 수 있다는 뜻이며
CONSTRUCTOR는 생성자를 선언할 때 붙일 수 있다는 뜻이며
. . .
TYPE_USE는 타입이 사용되는 모든 곳에 붙일 수 있다는 뜻이다.
@Retention
- @Retention은 애노테이션이 유지(retention)되는 기간을 지정하는데 사용된다.
유지 정책 | 의미 |
SOURCE | 소스 파일에만 존재. 클래스 파일에는 존재하지 않음 |
CLASS | 클래스 파일에 존재. 실행 시에 사용 불가. 기본 값 |
RUNTIME | 클래스 파일에 존재. 실행 시에 사용 가능 |
- 컴파일러에 의해 사용되는 애노테이션의 유지 정책은 SOURCE이다.
(컴파일러를 직접 작성할 것이 아니면, 이 유지 정책은 필요 없다.)
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public @interface Override {}
- 실행 시에 사용 가능한 애너테이션의 정책은 RUNTIME이다.
(유지 정책을 RUNTIME으로 하면, 실행 시에 리플렉션을 통해 클래스 파일에 저장된 애노테이션의 정보를 읽어서 처리할 수 있다.)
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface FunctionalInterface {}
@Documented
- @Documented는 애노테이션에 대한 정보가 javadoc으로 작성한 문서에 포함되도록 한다.
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface FunctionalInterface {}
@Inherited
- @Inherited는 애노테이션이 자손 클래스에 상속 되도록 한다.
- @Inherited가 붙은 애노테이션을 조상 클래스에 붙이면, 자손 클래스도 이 애노테이션이 붙은 것과 같이 인식된다.
@Inherited // @SupperAnno가 자손까지 영향 미치게
@interface SupperAnno {}
@SuperAnno
class Parent {}
class Child extends Parent {} // Child에 애노테이션이 붙은 것으로 인식한다.
@Repeatable
- @Repeatable는 반복해서 붙일 수 있는 애노테이션을 정의할 때 사용한다.
@interface ToDos{ // 여러 개의 ToDo 애노테이션을 담을 컨테이너 애노테이션 ToDos
ToDo[] value(); // ToDo 애노테이션 배열 타입의 요소를 선언. 이름이 반드시 value 이어야 함
}
// ToDo 애노테이션을 여러 번 반복해서 쓸 수 있게 한다.
@Repeatable(ToDos.class) // 괄호 안에 컨테이너 애노테이션을 지정해 줘야한다.
@interface ToDo {
String value();
}
- @Repeatable이 붙은 애너테이션은 반복해서 붙일 수 있다.
@ToDo("delete test codes.")
@ToDo("override inherited methods")
class MyClass {
...
}
@Native
- @Native는 native 메서드에 의해 참조되는 상수에 붙이는 애노테이션이다.
@Native public static final long MIN_VALUE = 0x8000000000000000L;
- 네이티브 메서드는 JVM이 설치된 OS의 메서드를 말한다.
네이티브 메서드는 보통 C 언어로 작성되어 있는데, 자바에서는 메서드의 선언부만 정의하고 구현은 하지 않는다.
- 자바에 정의된 네이티브 메서드와 OS의 메서드를 연결해주는 작업은 JNI(Java Native Interface)가 담당한다.
3.4 애노테이션 타입 정의하기
새로운 애노테이션을 정의하는 방법
@interface 애노테이션이름{
타입 요소이름(); // 애노테이션의 요소를 선언한다.
. . .
}
애노테이션의 요소
- 애노테이션의 요소(element)는 애노테이션 내에 선언된 메서드를 말한다.
- 아래에 선언된 TestInfo 애노테이션은 다섯 개의 요소를 갖는다.
@interface TestInfo {
int count();
String testedBy();
String[] testTools();
TestType testType(); // enum TestType { FIRST , FINAL }
DateTime testDate(); // 자신이 아닌 다른 애노테이션(@DateTime)을 포함 할 수 있다.
}
@interface DateTime {
String yymmdd();
String hhmmss();
}
- 애노테이션의 요소는 반환 값이 있고, 매개 변수는 없는 추상 메서드의 형태를 가진다.
그리고 애노테이션을 적용할 때 이 요소들의 값을 모두 지정 해야 한다.
(요소의 이름도 같이 적어주므로 순서는 상관 없다.)
@TestInfo(
count = 3, testedBy ="Kim",
testTools = {"JUnit", "AutoTester"},
testType = TestType.FIRST,
testDate = @DateTime(yymmdd = "160101", hhmmss = "235959")
}
public class NewClass { . . . }
애노테이션 요소의 기본 값
- 애노테이션을 적용 시 값을 지정하지 않으면, 사용 될 수 있는 요소의 기본 값을 지정 할 수 있다. (기본 값으로 null은 사용 불가)
@interface TestInfo {
int count() default 1;
}
@TestInfo // @TestInfo(count = 1)과 동일
public class NewClass { ... }
- 애노테이션의 요소가 하나이고 이름이 value인 경우, 애노테이션을 적용할 때 요소의 이름을 생략 하고 값만 적어도 된다.
@interface TestInfo {
String value();
}
@TestInfo("passed") // @TestInfo (count=5)와 동일
public class NewClass { ... }
- 요소의 타입이 배열인 경우, 중괄호 {}를 사용해서 여러 개의 값을 지정할 수 있다.
@interface TestInfo {
String[] testTools();
}
@TestInfo(testTools = {"JUnit", "AutoTester"}) // 값이 여러 개 인 경우
@TestInfo(testTools = "JUnit") // 값이 하나일 때는 괄호 {} 생략 가능
@TestInfo(testTools = {}) // 값이 없을 때는 괄호 {}가 반드시 필요
class NewClass { ... }
- 기본 값을 지정할 때도 괄호{}를 사용할 수 있다.
@interface TestInfo {
String[] info() default {"aaa", "bbb"}; // 기본 값이 여러 개인 경우, 괄호 {} 사용
String[] info2() default "ccc"; // 기본 값이 하나 인 경우, 괄호 생략 가능
}
@TestInfo // @TestInfo (info={"aaa", "bbb"}, info2="ccc")와 동일
@TestInfo(info2={}) // @TestInfo (info={"aaa", "bbb"}, info2={})와 동일
class NewClass { ... }
java.lang.annotation.Annotation
- Annotation은 모든 애노테이션의 조상이지만 상속은 불가능하다.
@interface TestInfo extends Annotation { // 에러. 허용되지 않는 표현
int count();
String testedBy();
. . .
}
- 사실, Annotation은 인터페이스로 정의되어 있다.
마커 애노테이션 (Marker Anntation)
- 마커 애노테이션은 요소가 하나도 정의되지 않은 애노테이션이다.
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public @interface Override {} // 마커 애노테이션. 정의된 요소가 하나도 없다.
애노테이션 요소의 규칙
- 애노테이션의 요소를 선언할 때 지켜야 하는 규칙은 다음과 같다.
① 요소의 타입은 기본형, 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(); // 에러. 요소의 타입에 타입 매개변수 사용 불가
}
'Java > 객체 지향 개념 ~' 카테고리의 다른 글
자바의 정석 (Chapter 11. 컬렉션 프레임워크) (0) | 2020.08.23 |
---|---|
자바의 정석 (Chapter 10. 날짜와 시간 & 형식화) (0) | 2020.08.19 |
자바의 정석 (Chapter 9. java.lang 패키지와 유용한 클래스) (0) | 2020.08.14 |
자바의 정석 (Chapter 8. 예외 처리) (1) | 2020.08.08 |
자바의 정석 (Chapter 7_3. 객체지향개념 2) (0) | 2020.08.03 |
댓글