2 minute read

item 31 한정적 와일드카드를 사용해 API 유연성을 높이라.

  • 매개변수화 타입은 기본적으로 불공변이다.
    • List<Object>에는 어떤 객체든 넣을 수 있지만 List<String>에는 문자열만 넣을 수 있다. 즉, List<String>List<Object>가 하는 일을 제대로 수행하지 못하니 하위 타입이 될 수 없다.
  • 하지만, 보다 유연한 API를 위해서는 일반적인 제너릭 타입보다는 한정적 와일드카드를 사용하는 것이 좋다.
    • API 유연성을 극대화하려면 매개변수에 와일드카드 타입을 사용하자.
// Stack<E> 에 다음과 같은 메서드를 추가한다고 가정하자.
public void pushAll(Iterable<E> src) {
	for (E e : src)
        push(e);
}

// 다음 코드는 Integer가 Number의 하위 타입이니 잘 동작해야 할 것 같지만,
// 실제로는 불공변이기 때문에 오류가 발생한다.
Stack<Number> numberStack = new Stack<>();
Iterable<Integer> integers = ...;
numberStack.pushAll(integers);

위 코드는 incompatible type 오류가 발생하며, 자바에서는 한정적 와일드카드 타입이라는 특별한 매개변수화 타입을 통해 해결할 수 있다.

StackTest.java:7: error: incompatible types: Iterable<Integer>
cannot be converted to Iterable<Number>
	numberStack.pushAll(integers);

위 에러 메시지는 EIterable이 아니라 E의 하위 타입의 Iterable이어야 한다는 뜻이며, 와일드카드 타입 Iterable<? extends E>가 정확히 이런 뜻을 의미한다.

// 와일드카드를 사용해 수정한 pushAll 메서드이다.
public void pushAll(Iterable<? extends E> src) {
	for (E e : src)
	    push(e);
}

위와 유사하게, popAll메서드도 한정적 와일드카드 타입을 사용해 작성할 수 있다. 이 경우 <? super T>를 사용한다.


public void popAll(Collection<? super E> dst) {
	while (!isEmpty())
		dst.add(pop());
}

생산자-소비자 관계에 따른 한정적 와일드카드

매개변수화 타입 T가 생산자라면 <? extends T>(upper bounded type)를 사용하고, 소비자라면 <? super T>(lower bounded type)를 사용하자.

  • 앞선 예에서 pushAll의 src 매개변수는 Stack이 사용할 E 인스턴스를 생산하므로 Iterable<? extends E>이다.
  • popAll의 dst 매개변수는 Stack으로부터 E 인스턴스를 소비하므로 dst의 적절한 타입은 Collection<? super E>이다.

이러한 생산자-소비자 관계를 정확하게 인지하지 않고 한정적 와일드카드를 사용해 API를 작성한다면, 클라이언트 코드에서도 와일드카드 타입을 신경써야 할 것이고 이는 좋지 않은 코드이다.

public static <E> Set<E> union(Set<? extends E> s1, Set<? extends E> s2)

Comparable 인터페이스와 한정적 와일드카드

public static <E extends Comparable<? super E>> E max(List<? extends E> list)
  • Comparable은 언제나 소비자이므로, 일반적으로 Comparable<E>보다는 Comparable<? super E>를 사용하는 편이 낫다.
  • 위와 마찬가지로, Comparator<E>보다는 Comparator<? super E>를 사용하는 편이 낫다.

제너릭과 와일드카드

메서드 내의 내부 처리에서는 제너릭을 사용하고, 외부로 노출되는 API에서는 와일드카드를 사용하자.
다시 말해, 메서드 선언에 타입 매개변수를 한 번 이상 사용한다면 와일드카드로 대체해야 한다.

// 다음 코드는 incompatible types 오류가 발생한다.
// list.get(i)에서 fresh type-variable 문제가 발생하는데, 
// 이 경우 제너릭을 사용한 메서드로 와일드카드의 실제 타입을 알려주는 메서드를 작성해 해결할 수 있다. 
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 도우미 메서드
private static <E> void swapHelper(List<E> list, int i, int j) {
	list.set(i, list.set(j, list.get(i)));
}

Leave a comment