지네릭스, 열거형, 어노테이션
: 다양한 타입의 객체들을 다루는 메서드나 컬렉션 클래스에 컴파일 시의 타입체크(compile-time type check)를 해주는 기능
/* 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)
Box< String> 클래스
class Box<String> { // 지네릭 타입을 String으로 지정
String item;
void setItem(String item) {
this.item = item;
}
String getItem() {
return item;
}
}
지네릭스의 용어
지네릭스의 제한
/* 지네릭스는 인스턴스별로 다르게 동작하려고 만든 기능 */
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[]로 형변환
지네릭 클래스의 객체 생성과 사용
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만 가능
제한된 지네릭 클래스
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 Edible { }
class FruitBox<T extends Edible> { ... }
// Fruit의 자손이면서 Eatable 인터페이스도 구현해야 한다는 제약
class FruitBox<T extends Fruit & Edible> { ... }
와일드 카드
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);
}
}
와일드카드 기호 ?
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);
}
}
class FruitBox<T extends Fruit> extends Box<T> { }
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()에서 Optional로 형변환이 가능하기 때문
- Optional -> Optional: 형변환 불가능
- Optional -> Optional<?> -> Optional: 형변환 가능
(경고)