매개변수와 타입은 불공변이다.
즉, 서로 다른 타입 Type1과 Type2가 있을 때 List
은 List<Type2>
의 하위 타입도 상위 타입도 아니다.
List
은 List
의 하위 타입이 아니라는 뜻이다.
List
에는 어떤 객체든 넣을 수 있지만 List
에는 문자열만 넣을 수 있다.
List
은 List
가 하는 일을 제대로 수행하지 못하니 하위 타입이 될 수 없는 것이다.
하지만 때론 불공변 방식보다 유연한 무엇인가가 필요한 경우가 있다.
그래서 자바는 한정적 와일드카드 타입이라는 특별한 매개변수화 타입을 지원한다. (
)
유연성을 극대화하려면 원소의 생산자나 소비자용 입력 매개변수에 와일드카드 타입을 사용해야 한다.
한편, 입력 매개변수가 생산자와 소비자 역할을 동시에 한다면 와일드카드 타입을 사용해도 좋을 게 없다.
타입을 정확히 지정해야 하는 상황으로, 이때는 와일드카드 타입을 쓰지 말아야 한다.
다만 다음 공식을 외워두면 어떤 와일드카드 타입을 써야 하는지 기억하는 데 도움이 될 것이다.
PECS : producer-extends, consumer-super
즉, 매개변수화 타입 T가 생산자라면 <? extends T>
를 사용하고, 소비자라면 <? super T>
를 사용하라.
(무언가를 위해 사용되면 생산자, 무언가를 사용하면 소비자)
public void pushAll(Iterable<? extends E> src) {
for (E e : src) {
push(e);
}
}
위의 pushAll
의 경우 내부 매서드에서 사용할 E 인스턴스를 생산하므로 src의 적절한 타입은 Iterable<? extends E>
가 될 수 있다.
public void popAll(Collection<? super E> dst) {
while (!isEmpty()) {
dst.add(pop());
}
}
하지만 popAll
의 경우 dst 매개변수는 E 인스턴스를 소비하므로 dst의 적절한 타입은 Collection
이다.
제대로만 사용한다면 클래스 사용자는 와일드카드 타입이 쓰였다는 사실조차 의식하지 못할 것이다.
받아들여야 할 매개변수를 받고 거절해야 할 매개변수는 거절하는 작업이 알아서 이뤄진다.
클래스 사용자가 와일드카드 타입을 신경 써야 한다면 그 API에 무슨 문제가 있을 가능성이 크다.
그리고 와일드카드와 관련해 논의해야 할 주제가 하나 더 있다.
타입 매개변수와 와일드카드에는 공통되는 부분이 있어서, 메서드를 정의할 때 둘 중 어느 것을 사용해도 괜찮을 때가 많다.
기본 규칙은 이렇다.
메서드 선언에 타입 매개변수가 한 번만 나오면 와일드카드로 대체하라.
이때 비한정 타입 매개변수라면 비한정적 와일드카드로 바꾸고, 한정적 타입 매개변수라면 한정적 와일드카드로 바꾸면 된다.