참고 : 자바의 정석
기호 '?' 표현하는데, 와일드카드는 어떤 타입도 될 수 있다.
아래 코드를 보자.
package com.javaex.generics;
import java.util.ArrayList;
import java.util.List;
public class FruitsBoxEx3 {
public static void main(String[] args) {
FruitBox<Fruit> fruitBox = new FruitBox<>();
FruitBox<Apple> appleBox = new FruitBox<>();
fruitBox.add(new Apple());
fruitBox.add(new Grape());
appleBox.add(new Apple());
System.out.println(Juicer.makeJuice(fruitBox));
System.out.println(Juicer.makeJuice(appleBox));
}
}
class Juice {
String name;
public Juice(String name) {
this.name = name + "Juice";
}
@Override
public String toString() {
return this.name;
}
}
class Juicer {
static Juice makeJuice(FruitBox<? extends Fruit> box) {
String tmp = "";
for(Fruit fruit: box.getList()) {
tmp += fruit + " ";
}
return new Juice(tmp);
}
}
class FruitBox<T extends Fruit> extends Box<T> {}
class Box<T> {
List<T> list = new ArrayList<>();
List<T> getList() {
return list;
}
void add(T item) {
list.add(item);
}
T getItem(int i) {
return list.get(i);
}
int size() {
return list.size();
}
@Override
public String toString(){
return list.toString();
}
}
Fruit, Grape, Apple 클래스는 제외했다. 이전 글에서 볼 수 있다.
핵심은 이 부분이다.
static Juice makeJuice(FruitBox<? extends Fruit> box)
만약, 와일드카드를 사용할 수 없다고 가정하자.
static Juice makeJuice(FruitBox<Fruit> box)
static Juice makeJuice(FruitBox<Apple> box)
static Juice makeJuice(FruitBox<Group> box)
이런 식으로 중복 정의했을 것이다. 그러나 이 코드는 컴파일 에러가 발생한다. 지네릭 타입이 다른 것만으로 오버로딩이 성립하지 않는다. 이 문제를 해결하는 것이 와일드카드'?'이다.
'?'자체는 Object와 같다. 'extends'와 'supper'로 상한과 하한을 제한한다.
<? extends T> // 와일드카드 상한 제한. T와 그 자손만 가능
<? super T> //와일드카드 하한제한. T와 그 조상들만 가능
<?> // == 제한 없음. 모든 타입 가능.(==<? extends Object>)
위 코드에서 와일드카드를 적용한 static메서드를 호출하는 부분
System.out.println(Juicer.makeJuice(fruitBox);
System.out.println(Juicer.makeJuice(appleBox);
와일드카드를 사용함으로써 모든 종류의 FruitBox를 매개변수의 인자로 받을 수 있다. 그러나 box의 요소가 Fruit의 자손이라는 보장이 없으므로 for문에서 box에 저장된 요소를 Fruit타입 참조변수로 받을 수 없다.
for(Fruit fruit : box.getList()) {
...
}
위 코드에서 box.getList()가 Fruit가 아닐 수도 있으므로 에러가 발생해야 맞지만,
class FruitBox<T extends Fruit> extends Box<T>
지네릭 클래스 FruitBox선언부에 이미 Fruit를 상속하는 타입만을 받도록 제한했기 때문에 아무 문제 없이 컴파일된다.
package com.javaex.generics;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
public class FruitBoxEx4 {
public static void main(String[] args) {
FruitBox<Apple> appleBox = new FruitBox<>();
FruitBox<Grape> grapeBox = new FruitBox<>();
appleBox.add(new Apple("Green Apple", 300));
appleBox.add(new Apple("Green Apple", 100));
appleBox.add(new Apple("Green Apple", 200));
grapeBox.add(new Grape("Pupple Grape", 400));
grapeBox.add(new Grape("Pupple Grape", 200));
grapeBox.add(new Grape("Pupple Grape", 300));
System.out.println("===========정렬 전===========");
System.out.println("appleBox : " + appleBox);
System.out.println("grapeBox : " + grapeBox);
System.out.println();
Collections.sort(appleBox.getList(), new FruitComp());
Collections.sort(grapeBox.getList(), new FruitComp());
System.out.println("===========정렬 후===========");
System.out.println("appleBox : " + appleBox);
System.out.println("grapeBox : " + grapeBox);
}
}
class Fruit {
String name;
int weight;
public Fruit(String name, int weight) {
this.name = name;
this.weight = weight;
}
@Override
public String toString() {
return name + "("+weight+")";
}
} //end - Fruit
class Apple extends Fruit {
public Apple(String name, int weight) {
super(name, weight);
}
}
class Grape extends Fruit {
Grape(String name, int weight) {
super(name, weight);
}
}
class FruitComp implements Comparator<Fruit> { //내림차순
@Override
public int compare(Fruit o1, Fruit o2) {
return o2.weight - o1.weight;
}
}
class AppleComp implements Comparator<Apple> {
@Override
public int compare(Apple o1, Apple o2) {
return o2.weight - o1.weight;
}
}
class GrapeComp implements Comparator<Grape> {
@Override
public int compare(Grape o1, Grape o2) {
return o2.weight - o1.weight;
}
}
class FruitBox<T extends Fruit> extends Box<T> { }
class Box<T> {
List<T> list = new ArrayList<>();
void add(T item) {
list.add(item);
}
T get(int i) {
return list.get(i);
}
List<T> getList() {
return list;
}
int size() {
return list.size();
}
@Override
public String toString() {
return list.toString();
}
}
Collections의 sort()를 이용해서 무게별로 정렬하는 예제다.
static <T> void sort(List<T> list, Comparator<? super T> c>
sort()의 선언부다.
만약 와일드 카드를 사용하지 않았다고 가정한다면
static <T> void sort(List<T> list, Comparator<T> c)
선언부는 이렇게 된다. <T>에 Apple을 대입하면 이렇게 바뀐다.
static <Apple> void sort(List<Apple> list, Comparator<Apple> c)
List<Apple>를 정렬하기 위해 Comparator<Apple>가 필요하다. 그래서 Comparator<Apple>를 구현한 클래스를 만들어야 한다.
class AppleComp implements Comparator<Apple> {
@Overrride
public int compare(Apple o1, Apple o2) {
return o2.weight - o1.weight;
}
}
만약 Apple이 아니고 Grape가 대입된다면?
똑같이 Comparator<Grape>를 구현한 클래스를 하나 더 정의해야 한다.
타입만 다를 뿐 완전히 똑같은 클래스를 만들어야 한다는 것은 낭비다. 이 문제를 해결하기 위해 타입 매개변수에 하한 제한(super) 와일드카드를 적용해둔 것이다.
그럼 Apple이 들어오든, Grape이 들어오든 상관 없이 정렬할 수 있다.
static <Apple> void sort(List<Apple> list, Comparator<? super Apple> c>
static <Grape> void sort(List<Grape> list, Comparator<? super Grape> c>
Comparator <? super Apple>이라는 의미는 Apple과 그 조상이 가능하다는 뜻이다.
Comparator <Apple>, Comparator<Fruit>, Comparator<Object>
그래서
class FruitComp implements Comparator<Fruit> {
@Override
public int compare(Fruit o1, Fruit o2) {
return o2.weight - o1.weight;
}
}```
이렇게 정의해두면 Apple과 Grape을 모두 정렬할 수 있다.
### 질문
- 왜 와일드카드 대신 <T>로 받지 못하나?