제네릭의 기본인 1.1~1.3은 생략함
1.4 제한된 지네릭 클래스
다음과 같이 작성하면 Fruit의 자손타입만 T에 대입할 수 있다. (= Fruit를 implements 또는 extends한 클래스만 T에 대입할 수 있다.) 좀 더 엄격하게 FruitBox에 들어올 수 있는 타입을 관리할 수 있다.
1class FruitBox<T extends Fruit>{
2 ArrayList<T> list = new ArrayList<T>();
3 ...
4}
Fruit의 자손 인스턴스는 얼마든지 담을 수 있다. 다형성을 이용해 여러 과일을 담을 수 있게 되었다.
1FruitBox<Fruit> fruitBox = new FruitBox<Fruit>();
2fruitBox.add(new Apple()); // Apple이 Fruit의 자손
3fruitBox.add(new Grape()); // Grape는 Fruit의 자손
클래스가 아닌 인터페이스를 구현해야하는 경우 아래와 같이 작성하면 된다. 주의할 점은 implements 키워드 대신에 extends를 사용한다는 점이다.
1interface Eatable {}
2class FruitBox<T extends Eatable> {...}
Fruit의 자손이면서 Eatable 인터페이스도 구현해야 하는 경우 아래와 같이 작성한다.
1class Fruit<T extends Fruit & Eatable> {...}
FruitBox에는 Fruit의 자손이면서 Eatable을 구현한 클래스만 타입 매개변수 T에 대입될 수 있다.
1import java.util.ArrayList;
2
3interface Eatable { }
4
5class Fruit implements Eatable{
6 public String toString(){ return "Fruit";}
7}
8
9class Apple extends Fruit { public String toString() { return "Apple";}}
10class Grape extends Fruit { public String toString() { return "Grape";}}
11class Toy { public String toString() { return "Toy";}}
12
13public class App {
14 public static void main(String[] args) {
15 FruitBox<Fruit> fruitBox = new FruitBox<>();
16 FruitBox<Apple> appleBox = new FruitBox<>();
17 FruitBox<Grape> grapeBox = new FruitBox<>();
18 //FruitBox<Grape> grapeBox = new FruitBox<Apple>();//에러. 타입불일치
19 //FruitBox<Toy> toyBox = new FruitBox<Toy>(); //에러. 제네릭을 지원하지 않음.
20
21 fruitBox.add(new Fruit());
22 fruitBox.add(new Apple());
23 fruitBox.add(new Grape());
24 appleBox.add(new Apple());
25 //appleBox.add(new Grape()); // 에러. Grape는 Apple의 자손이 아니다.
26 grapeBox.add(new Grape());
27
28 System.out.println("fruitBox = " + fruitBox);
29 System.out.println("appleBox = " + appleBox);
30 System.out.println("grapeBox = " + grapeBox);
31
32 }
33
34}
35
36class Box<T>{
37 ArrayList<T> list = new ArrayList<T>();
38 void add(T item){ list.add(item);}
39 T get(int i){return list.get(i);}
40 int size(){return list.size();}
41 public String toString(){return list.toString();}
42}
43
44class FruitBox<T extends Fruit & Eatable> extends Box<T>{
45}
1.5 와일드 카드
FruitBox의 내용물을 통해 Juice 인스턴스를 반환하는 Juicer 클래스의 스태틱 메소드.
여기서 makeJuice의 파라미터인 FruitBox의 타입 매개변수가 Fruit
임에 주목한다.
1class Juicer{
2 static Juice makeJuice(FruitBox<Fruit> box){
3 String tmp = "";
4 for(Fruit f : box.getList()) tmp += f + " ";
5 return new Juice(tmp);
6 }
7}
static 메소드는 타입 매개변수를 사용할 수 없으므로 아예 지네릭스를 적용하지 않던지 위처럼 특정 타입 매개변수를 지정해 줘야한다.
1FruitBox<Fruit> fruitBox = new FruitBox<>();
2FruitBox<Apple> appleBox = new FruitBox<>();
3...
4
5System.out.println(Juicer.makeJuice(fruitBox));
6System.out.println(Juicer.makeJuice(appleBox)); // 에러.
이 문제를 해결하기 위한 가장 단순한 방법은 makeJuice를 매개변수 타입별로 여러개 만드는 것이지만, 자바 컴파일에선 지네릭 타입이 다른 것만으로 오버로딩이 성립하지 않는다고 인식하기 때문에 에러가 발생한다. 즉, 오버로딩이 아닌 메소드 중복이 발생한다. 이 문제를 해결하기 위해 고안된 것이 와일드 카드
이다.
와일드 카드는 아래의 문법을 따른다.
<? extends T>
는 와일드 카드의 상한 제한. T와 그 자손들만 가능하다.
<? super T>
는 와일드 카드의 하한 제한. T와 그 조상들만 가능하다.
<?>
는 제한없음. 모든 타입이 가능. <? extends Object>
와 동일하게 동작한다.
와일드 카드를 사용해 makeJuice()의 매개변수 타입을 변경해보자.
1class Juicer{
2 // FruitBox에는 타입 매개변수로 Fruit와 그 자손들만 올 수 있다.
3 static Juice makeJuice(FruitBox<? extends Fruit> box){
4 String tmp = "";
5 for(Fruit f : box.getList()) tmp += f + " ";
6 return new Juice(tmp);
7 }
8}