[Effective Java] 5 - item 31 한정적 와일드카드를 사용해 API 유연성을 높이라.
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);
위 에러 메시지는 E
의 Iterable
이 아니라 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