당신은 아래의 코드를 읽을 수 있는가?
public static <T extends Comparable<? super T>> void sort(List<T> list)
조금 어렵다면 이 글을 읽어보자
다양한 타입의 객체들을 다루는 method, collection에 컴파일 시점에 타입 체크를 해주는 기능
→ 컴파일 시점에 체크를 하므로 객체 타입 안정성을 높이고 형변환의 번거로움이 줄어든다
class Box {
private Object item;
public void setItem(Object item) { this.item = item; }
public Object getItem() { return this.item; }
}
Box 클래스는 Object 타입의 item을 멤버 변수로 작성되어졌다.
Box 클래스의 item을 사용하려면 아래와 같이 형 변환을 해야 하는 번거로움이 있다.
→컴파일러는 item의 타입이 String인지 모르기 때문
Box myBox = new Box();
myBox.setItem("my item");
String myItem = (String) myBox.getItem(); // 형변환 필요
이러한 번거로움을 해결하기 위해 컴파일 시점에 타입을 체크할 수 있는 Generics가 도입이 되었다.
class Box<T,M> {
private T item;
private M shape;
public void setItem(T item) { this.item = item; }
public T getItem() { return item; }
}
class Box<T extends Fruit > {};
→ Fruit의 자식 클래스만 허용public static void main(String[] args) {
Box<String,Rectangle> myBox = new Box(); // T, M 대신 실제 타입 지정
myBox .setItem("my item");
String myItem = myBox .getItem();
}
Box<Apple> appleBox = new Box<Banana>();
: 불가능매개변수에 FruitBox<Fruit> fruitBox
를 받아야 하는 static method가 있다.
class Juicer<T extends Fruit> {
// 단순 Juice를 만들어 주는 역할을 하는 class 이므로 static method로 사용한다.
static Juice makeJuice(FruitBox<T> box) { //오류 : static method의 타입 매개변수 불가!
String tmp = "";
for (Fruit f : box.getList()) tmp += f + " ";
return new Juice(tmp);
}
}
그렇다면 makeJuice를 오버로딩을 해서 사용해야 하는가?
class Juicer{
static Juice makeJuice(FruitBox<Banana> 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);
}
}
→ Generics는 컴파일할 때만 사용하므로 위 두 method는 method 중복 정의에 해당됨.
Generics은 static method에서 Generics 타입 매개변수를 사용 할 수 없다. 이를 해결하기 위해 Wildcard가 고안되었다.
<? extends T>
: T와 자손들 가능 (상한제한)<? super T>
: T와 그 조상들 가능 (하한제한)<?>
: 제한 없음class Juicer{
static Juice makeJuice(FruitBox<? extends Fruit> box){ //와일드 카드사용
String tmp = "";
for (Fruit f : box.getList()) tmp += f + " ";
return new Juice(tmp);
}
}
Method의 선언부에 Generic 타입이 선언된 method이다.
class FruitBox<T extends Fruit & Eatable> extends Box<T> {
static <M> void sort(List<M> list, Comparator<? super M> c){}
}
Generic 클래스에 정의된 타입 매개변수 T와 Generic Method에 정의된 타입 매개변수M은 전혀 별개다.
static 메서드, 변수는 인스턴스 변수를 참조할 수 없기 때문에 static 메서드, 변수는 Generic을 사용 할수 없다고 했다. 그러나 Generic method를 사용하면 다르다.
static <T extends Fruit> Juice makeJuice(FruitBox<T> box) {}
→ 매개변수에 선언할 T에 대한 자료형을 미리 선언하게 되므로 문제가 없어진다!
글 처음에 본 Collections.sort
를 보자 이제는 해석이 가능해 질 것이다.
public static <T extends Comparable<? super T>> void sort(List<T> list)
<T extends Comparable<? super T>
는 Generic Method임을 명시한다.
extends Comparable<? super T>
는 T 의 조상 클래스를 허용하는 wildcard 이다.
즉, List<T>
타입의 인스턴스( ex:List<Integer>
)를 미리 생성하고 sort 매개변수에 넣어 호출하게 된다.