// 매개변수로 과일박스를 받아 주스를 반환하는 Juicer 클래스
class Juicer {
static Juice makeJuice(FruitBox<Fruit> box) { // <Fruit>로 지정
Strint 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.makeJuice(fruitBox)); // OK. FruitBox<Fruit>
System.out.println(Juicer.makeJuice(appleBox)); // 에러. FruitBox<Apple>
// 지네릭 타입만 변경해서 같은 이름 메서드 정의 예제(메서드 중복정의)\
static Juice makeJuice(FruitBox<Fruit> box) {
Strint tmp = "";
for(Fruit f : box.getList()) tmp += f + " ";
return new Juice(tmp);
}
static Juice makeJuice(FruitBox<Apple> box) {
Strint tmp = "";
for(Fruit f : box.getList()) tmp += f + " ";
return new Juice(tmp);
}
💡 이런 불편함을 개선하고자 와일드 카드가 고안 되었다.
와일드 카드는 보통 지네릭 타입을 지정하는 곳에 '?'로 표현한다. 예시를 들면
// 와일드 카드 예시 코드
FruitBox<? extends Fruit>
위와 같은 방식으로 사용할 수 있다. <?>만 사용하게 되면 모든 타입이 전부 대입이 가능하기 때문에 Object와 다를 것이 없다. 따라서 상속관계를 이용한 상한과 하한의 제한을 둘 수 있다.
💡 지네릭 클래스와는 다르게 와일드 카드에는 '&'를 사용할 수 없다.
와일드 카드를 이용해서 makeJuice( )를 수정한다면 아래와 같이 변경된다.
static Juice makeJuice(FruitBox<? extends Fruit> box) {
String tmp = "";
for(Fruit f : box.getList()) tmp += f + " ";
return new Juice(tmp);
}
위와 같이 와일드 카드를 사용하면 Fruit 뿐만 아니라 자손 객체인 Apple과 Grape도 사용이 가능하게 된다.
💡 같은 타입 문자 T를 사용해도 지네릭 클래스에 정의된 타입 매개변수와
지네릭 메서드에 정의된 타입 매개변수는 전혀 별개의 것임에 주의해야
한다.
// 지네릭 메서드 예제
class FruitBox<T> {
...
static <T> void sort(List<T> list, Comparator<? super T> c) {
// 클래스에 선언된 타입변수 T와 sort() 메서드에 매개변수 타입변수 T는
// 문자만 같을 뿐 서로 다른 것이다.
...
}
}
지네릭 메서드를 정의하고 호출하는 방법은 아래의 코드와 같다.
// 지네릭 메서드 정의
static <T extends Fruit> Juice makeJuice(FruitBox<T> box) {
String tmp = "";
for(Fruit f : box.getList()) tmp += f + " ";
return new Juice(tmp);
}
// 지네릭 메서드 호출
// FruitBox 생성
FruitBox<Fruit> fruitBox = new FruitBox<Fruit>();
FruitBox<Apple> appleBox = new FruitBox<Apple>();
// 지네릭 메서드 makeJuice 호출
System.out.println(Juicer.<Fruit>makeJuice(fruitBox));
System.out.println(Juicer.<Apple>makeJuice(appleBox));
아래의 지네릭 메서드는 Collections 클래스의 sort( ) 메서드이다.
와일드카드와 지네릭 타입이 복잡하게 사용되있어서 이해하기가 어렵다.
아래의 메서드를 순차적으로 뜯어서 분석해보면 다음과 같다.
// 지네릭 타입의 형변환 예제
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>
1. 지네릭 타입의 경계(bound)를 제거한다.
// 지네릭 타입 제거 전
class Box<T extends Fruit> {
void add(T t) {
...
}
}
// 지네릭 타입 제거 후
class Box {
void add(Fruit t) {
...
}
}
2. 지네릭 타입을 제거한 후에 타입이 일치하지 않으면, 형변환을 추가한다.
// 지네릭 타입 제거 전
T get(int i) {
return list.get(i);
}
// 지네릭 타입 제거 후
Fruit get(int i) {
return (Fruit)list.get(i);
}
2-1 와일드 카드가 포함되어 있는 경우의 형변환 추가
// 지네릭 타입 제거 전
static Juice makeJuice(FruitBox<? extends Fruit> box) {
String tmp = "";
for(Fruit f : box.getList()) tmp += f + " ";
return new Juice(tmp);
}
// 지네릭 타입 제거 후
static Juice makeJuice(FruitBox<? extends Fruit> box) {
String tmp = "";
Iterator it = box.getList().iterator();
while(it.hasNext()) {
tmp += (Fruit)it.next() + " ";
}
return new Juice(tmp);
}