매개변수에 과일박스를 대입하면 주스를 만들어서 변환하는 Juicer라는 클래스가 있고, 이 클래스에는 과일을 주스로 만들어서 변환하는 makeJuice()라는 static메서드가 다음과 같이 정의되어 있다고 가정하자.
class Juicer {
static Juice makeJuice(FruitBox<Fruit> box) {
String tmp = "";
for (Fruit f : box.getList()) {
tmp += f + " ";
}
return new Juice(tmp);
}
}
Juicer 클래스는 지네릭 클래스가 아닌데다, 지네릭 클래스라고 해도 static 메서드에서는 타입 매개변수 T를 매개변수에 사용할 수 없으므로 아예 지네릭스를 적용하지 않던가, 위와 같이 타입 매개변수 대신, 특정 타입을 지정해줘야 한다.
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)); //에러. FruitBox<Apple>
이렇게 지네릭 타입을 'FruitBox<Fruit>'
로 고정해 놓으면, 위의 코드에서 알 수 있듯이 'FruitBox<Apple>'
타입의 객체는 makeJuice()의 매개변수가 될 수 없으므로, 여러 가지 타입의 매개변수를 갖는 makeJuice()를 만들 수 밖에 없다.
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>와 동일
와일드 카드를 사용해서 makeJuice()의 매개변수 타입을 FruitBox<Fruit>
에서 FruitBox <? extends Fruit>으로 바꾸면 다음과 같이 된다.
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<? extends Object>
로 하면, 모든 종류의 FruitBox가 이 메서드의 매개변수로 가능해진다. 대신, 전과 달리 box의 요소가 Fruit의 자손이라는 보장이 없으므로 아래의 for문에서 box에 저장된 요소를 Fruit타입의 참조변수로 못받는다.
static Juice makeJuice(FruitBox<? extends Object> box) {
String tmp = "";
for(Fruit f : box.getList()) tmp += f + " "; //에러. Fruit이 아닐 수 있음
return new Juice(tmp);
}
그러나 실제로 테스트 해보면 문제없이 컴파일되는데 그 이유는 바로 지네릭 클래스 FruitBox를 제한했기 때문이다.
class FruitBox<T extends Fruit> extends Box<T> {}
컴파일러는 위 문장으로부터 모든 FruitBox의 요소들이 Fruit의 자손이라는 것을 알고있기에 문제삼지 않는다.
static <T> void sort(List<T> list, Comparator<? super T> c)
'static' 옆에 있는 ''는 메서드에 선언된 지네릭 타입이다. 이런 메서드를 지네릭 메서드라고 하는데, 다음 단원에서 배울 것이다. 첫 번째 매개변수는 정렬할 대상이고, 두 번째 매개변수는 정렬할 방법이 정의된 Comparator 이다. Comparator의 지네릭 타입에 하한 제한이 걸려있는 와일드 카드가 사용되었다. 먼저 다음과 같이 와일드 카드를 사용하지 않았다고 가정해보자.
static <T> void sort(List<T> list, Comparator<T> c)
만일 타입 매개변수 T에 Apple이 대입되면, 위의 정의는 아래와 같이 바뀔 것이다.
static void sort(List<Apple> list, Comparator<Apple> c)
이것은 List<Apple>
을 정렬하기 위해서는 Comparator<Apple>
이 필요하다는 것을 의미한다. 그래서 Comparator<Apple>
을 구현한 AppleComp 클래스를 아래처럼 정의하였다.
class AppleComp implements Comparator<Apple> {
public int compare(Apple t1, Apple t2) {
return t2.weight - t1.weight;
}
}
하지만 Apple 대신 Grape가 대입된다면 또 Comparator<Grape>
가 필요하다.
AppleComp와 GrapeComp는 타입만 다를 뿐 완전히 같은 코드이다. 코드의 중복도 문제지만, 새로운 Fruit의 자손이 생길 때마다 위와 같은 코드를 반복해서 만들어야 한다는 것이 더 문제이다.
static <T> void sort(List<T> list, Comparator<? super T> c)
위의 문장에서 타입 매개변수 T에 Apple이 대입되면, 다음과 같이 된다.
static void sort(List<Apple> list, Comparator<? super Apple> c)
매개변수의 타입이 Comparator<? super Apple>
이라는 의미는 Comparator의 타입 매개변수로 Apple과 그 조상이 가능하다는 뜻이다. 즉, Comparator<Apple>
, Comparator<Fruit>
,Comparator<Object>
중의 하나가 두 번째 매개변수로올 수 있다는 뜻이다.
그래서 아래와 같이 FruitComp를 만들면, List과 List를 모두 정렬할 수 있다.
class AppleComp implements Comparator<Fruit> {
public int compare(Apple t1, Apple t2) {
return t2.weight - t1.weight;
}
}
이러한 장점 때문에 Comparator에는 항상 <? super T>
가 습관적으로 따라 붙는다.