[기본 개념] 7 | (1.2) 와일드 카드, 지네릭 메서드, 지네릭 타입 제거
1 지네릭스란?
2 지네릭 클래스의 선언
3 지네릭 클래스의 객체 생성과 사용
4 제한된 지네릭 클래스
5> 와일드 카드
6> 지네릭 메서드
7> 지네릭 타입의 제거
5. 와일드 카드
매개변수에 과일박스를 대입하면 주스를 만들어서 반환하는 Juicer라는 클래스가 있다고 하자.
그러면 Juicer클래스는 지네릭 클래스가 아니고, static메서드는 타입 매개변수 T를 사용할 수 없으므로 'FruitBox<Fruit>'과 같이 특정 타입을 지정해야 한다. 이렇게 고정하면 'FruitBox<Apple>'타입의 객체는 매개변수가 될 수 없으므로 다음과 같이 여러 타입의 매개변수를 갖는 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) ;
}
하지만 위와 같이 오버로딩하면 컴파일 에러가 발생한다. 지네릭 타입이 다른 것만으로는 오버로딩이 성립하지 않기 때문이다. 지네릭 타입은 컴파일 시에만 사용되며 컴파일 후 제거한다. 따라서 '메서드 중복 정의'가 되는 것이다.
이럴 때, 기호 '?'로 표현하여 '와일드 카드'라는 것을 사용할 수 있다.
<? extends T> 와일드 카드의 상한 제한. T와 그 자손들만 가능
<? super T> 와일드 카드의 하한 제한. T와 그 조상들만 가능
<?> 제한없음. 모든 타입이 가능. <? extends Object>와 동일
예제/FruitBoxEx4.java
import java.util.*;
class Fruit {
String name;
int weight;
Fruit(String name, int weight) {
this.name = name;
this.weight = weight;
}
public String toString() { return name + "(" + weight + ")"; }
}
class Apple extends Fruit {
Apple(String name, int weight) {
super(name, weight);
}
}
class Grape extends Fruit {
Grape(String name, int weight) {
super(name, weight);
}
}
class AppleComp implements Comparator<Apple> {
public int compare(Apple t1, Apple t2) {
return t2.weight - t1.weight;
}
}
class GrapeComp implements Comparator<Grape> {
public int compare(Grape t1, Grape t2) {
return t2.weight - t1.weight;
}
}
class FruitComp implements Comparator<Fruit> {
public int compare(Fruit t1, Fruit t2) {
return t1.weight - t2.weight;
}
}
public class FruitBoxEx4 {
public static void main(String[] args) {
FruitBox<Apple> appleBox = new FruitBox<Apple>();
FruitBox<Grape> grapeBox = new FruitBox<Grape>();
appleBox.add(new Apple("GreenApple", 300));
appleBox.add(new Apple("GreenApple", 100));
appleBox.add(new Apple("GreenApple", 200));
grapeBox.add(new Grape("GreenGrape", 400));
grapeBox.add(new Grape("GreenGrape", 300));
grapeBox.add(new Grape("GreenGrape", 200));
Collections.sort(appleBox.getList(), new AppleComp());
Collections.sort(grapeBox.getList(), new GrapeComp());
System.out.println(appleBox);
System.out.println(grapeBox);
System.out.println();
Collections.sort(appleBox.getList(), new FruitComp());
Collections.sort(grapeBox.getList(), new FruitComp());
System.out.println(appleBox);
System.out.println(grapeBox);
}
}
class FruitBox<T extends Fruit> extends Box<T> {}
class Box<T> {
ArrayList<T> list = new ArrayList<T>();
void add(T item) { list.add(item); }
T get(int i) { return list.get(i); }
ArrayList<T> getList() { return list; }
int size() { return list.size(); }
public String toString() { return list.toString(); }
실행결과
[GreenApple(300), GreenApple(200), GreenApple(100)]
[GreenGrape(400), GreenGrape(300), GreenGrape(200)]
[GreenApple(100), GreenApple(200), GreenApple(300)]
[GreenGrape(200), GreenGrape(300), GreenGrape(400)]
이 예제는 Collections.sort( )를 이용해서 appleBox와 grapeBox에 담긴 과일을 무게별 정렬한다. 메서드의 선언부는 다음과 같다.
static <T> void sort(List<T> list, Comparator<? super T> c)
첫 번째 매개변수는 정렬할 대상이고, 두 번째 매개변수는 정렬할 방법이 정의된 Comparator이다.
와일드 카드가 사용되지 않았다면, Apple이 대입됐을 때와 Grape가 대입됐을 때 각각 같은 코드를 적어야 한다. 또한 새로운 자손이 생길 때마다 추가해야 한다. 따라서 하한 제한의 와일드 카드를 사용하여 문제를 해결할 수 있다.
Comparator<? super Apple> : Comparator<Apple>, Comparator<Fruit>, Comparator<Object>
Comparator<? super Grape> : Comparator<Grape>, Comparator<Fruit>, Comparator<Object>
6. 지네릭 메서드
메서드의 선언부에 지네릭 타입이 선언된 메서드를 지네릭 메서드라 한다. Collections.sort( )가 지네릭 메서드이며, 지네릭 타입의 선언 위치는 반환타입 바로 앞이다.
static <T> void sort(List<T> list, Comparator<? super T> c)
그리고 지네릭 클래스에 정의된 타입 매개변수와 지네릭 메서드에 정의된 타입 매개변수는 같은 타입 문자 T를 사용해도 같은 것이 아니다.
또한, static멤버에는 타입 매개변수를 사용할 수 없지만, 메서드에 지네릭 타입을 선언하면 사용할 수 있다.
public static <T extends Comparable<? super T>> void sort (List<T> list)
-> 'T'또는 그 조상의 타입(<? super T>)을 비교하는 Comparable을 구현하는(T extends Comparable) 타입 T를 요소로 하는 List(List<T>)를 매개변수로 허용한다.
7. 지네릭 타입의 제거
1 지네릭 타입의 경계를 제거한다.
지네릭 타입이 <T extends Fruit>라면 T는 Fruit로 치환된다. <T>인 경우는 T는 Object로 치환되며 클래스 옆의 선언은 제거된다.
2 지네릭 타입을 제거한 후 타입이 일치하지 않으면 형변환을 한다.
List의 get( )은 Object타입을 반환하므로 형변환이 필요하다.
T get (int i) { return list.get(i) ; } |
----> | Fruit get (int i) { return (Fruit) list.get(i) ; } |
와일드 카드가 포함되어 있는 경우
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) ; } |
출처 | Java의 정석 (남궁 성)
'💠프로그래밍 언어 > Java' 카테고리의 다른 글
[기본 개념] 8 | (1.2) 스레드 우선순위, 스레드 그룹, 데몬 스레드 (0) | 2022.01.20 |
---|---|
[기본 개념] 8 | (1.1) 프로세스, 스레드, Start( ), Run( ), 싱글/멀티 스레드 (0) | 2022.01.18 |
[기본 개념] 7 | (1.1) 지네릭스 클래스 (0) | 2022.01.17 |
[기본 개념] 6 | (1.11) Collections (0) | 2022.01.04 |
[기본 개념] 6 | (1.10) TreeMap, Properties (0) | 2022.01.04 |