매개변수화 타입은 기본적으로 불공변이다. 불공변 방식보다 유연한 무언가가 필요한 경우가 있다. Stack 클래스에 pushAll 메소드를 추가해야 한다고 가정해보자.
public void pushAll(Iterable<E> src) {
for (E e : src)
push(e);
}
.
.
.
Stack<Number> numberStack = ...;
numberStack.pushAll(integers);
Integer
는 Number
의 하위 타입이기 때문에 논리적으로 잘 동작해야 할 것 같지만 오류가 나온다. 이는 매개변수화 타입이 불공변이기 때문이다.public void pushAll(Iterable<? extends E> src) {
for(E e : src)
push(e);
}
public void popAll(Collection<E> dst) {
while(!isEmpty())
dst.add(pop());
}
.
.
.
Stack<Number> numberStack = ... ;
Collection<Object> objects = ... ;
numberStack.popAll(objects);
public void popAll(Collectio <? super E> dst) {
while(!isEmpty())
dst.add(pop());
}
유연성을 극대화하기 위해서는 원소의 생성자나 소비자용 입력매개변수에 와일드카드를 사용하라. 한편 입력 매개변수가 생산자와 소비자의 역할을 동시에 한다면 와일드 카드를 써도 좋을 것이 없다.
producer-extends, consumer-super
매개변수가 생산자라면 <? extends E>
, 소바자라면 <? super E>
를 사용하라는 뜻이다.
public static <E> Set<E> union(Set<? extends E> s1, Set<? extends E> s2) {
Set<E> result = new HashSet<>(s1);
result.addAll(s2);
return result;
}
public static <E extends Comparable<? super E>> E max(Collection<? extends E> c) {
if (c.isEmpty())
throw new IllegalArgumentException("Empty Collection");
E result = null;
for (E e : c)
if (result == null || e.compareTo(result) > 0)
result = Objects.requireNonNull(e);
return result;
}
Collection<E>
보다 <? extends E>
가 더 낫다.Comparable<E>
보다 Comparable<? super E>
가 더 낫다.타입 매개변수와 와일드 카드는 공통 분모가 많아 어느 것을 사용해도 괜찮을 때가 많다.
public static <E> void swap(List<E> list, int i, int j);
public static void swap(List<?> list, int i, int j);
하지만 두 번째 코드에는 문제가 있다. 다음의 코드는 컴파일되지 않는다.
public static void swap(List<?> list, int i, int j) {
list.set(i, list.set(j, list.get(i)));
}
public static void swap(List<?> list, int i, int j) {
swapHelper(list, i, j);
}
private static <E> void swapHelper(List<E> list, int i, int j) {
list.set(i, list.set(j, list.get(i)));
}