매게 변수화 타입은 불공변(invariant)이다. 즉 서로 다른 타입 Type1과 Type2가 있을때 List<Type1>
은 List<Type2>
의 하위 타입도 상위 타입도 아니다.
즉 List<String>
은 List<Object>
와 아무 관계도 아니라는 말이다.
이처럼 매개변수화 타입은 고정되어 있지만 때로는 좀 더 유연하게 사용하고 싶은 경우가 있을 수 있다.
public class Stack<E> {
public Stack();
public void push(E e);
public E pop();
public boolean isEmpty();
}
여기에 일련의 원소를 스택에 넣어보자
public void pushAll(Iterable<E> src) {
for (E e: src)
push(e);
}
만약에 이 Stack을 Number로 선언한 뒤 pushAll(intVal)을 호출하면 어떻게 될까? 여기서 intVal은 Integer 타입이다
Stack<Number> numberStack = new Stack();
Iterable<Integer> integers = ...;
numberStack.pushAll(integers); // 오류 메세지!
이렇듯이 매개 변수화 타입은 유연하게 사용하기 어렵다
자바는 이러한 상황에 대처할 수 있는 한정적 와일드 카드 타입을 지원한다
pushAll의 입력 매개변수 타입은 'E의 Iterable'이 아니라 'E의 하위 타입의 Iterable'이여야 한다.
public void pushAll(Iterable<? extends E> src) {
for (E e: src)
push(e);
}
이렇게 수정해주면 해결 가능하다!
이번엔 pushAll과 짝을 이루는 popAll을 살펴볼 차례다
public void popAll(Collection<E> dst) {
while(!isEmpty())
dst.add(pop());
}
이번에도 문제는 발생한다. Stack<Number>
의 원소를 Object용 컬렉션으로 옮기려 한다고 해보자.
Stack<Number> numberStack = new Stack();
Collection<Object> objects = ...;
numberStack.popAll(objects); // 오류 발생!!
이 코드는 하위 타입이 아니라는 오류가 발생한다.
이번에도 와일드 카드 타입으로 해결 가능하다
public void popAll(Collection<? super E> dst) {
while(!isEmpty())
dst.add(pop());
}
이제 모두 말끔히 컴파일 된다.
유연성을 극대화 하기 위해서는 생산자나 소비자용 매개변수에 와일드 카드 타입을 사용하자.
PECS: producer-extends, consumer-super
일련의 공식으로 외워두면 어떤 와일드 카드 타입을 써야할지 알 수 있을것이다.