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

신명철·2022년 2월 23일
0

Effective Java

목록 보기
29/80

매개변수화 타입은 기본적으로 불공변이다. 불공변 방식보다 유연한 무언가가 필요한 경우가 있다. Stack 클래스에 pushAll 메소드를 추가해야 한다고 가정해보자.

public void pushAll(Iterable<E> src) {
	for (E e : src)
		push(e);
}
.
.
.
Stack<Number> numberStack = ...;
numberStack.pushAll(integers);
  • 위의 경우 IntegerNumber의 하위 타입이기 때문에 논리적으로 잘 동작해야 할 것 같지만 오류가 나온다. 이는 매개변수화 타입이 불공변이기 때문이다.
  • 한정적 와일드카드 타입이라는 매개변수화 타입을 이용해 이를 해결할 수 있다.
public void pushAll(Iterable<? extends E> src) {
    for(E e : src)
        push(e);
}
  • 다음은 popAll 메소드이다.
public void popAll(Collection<E> dst) {
	while(!isEmpty())
		dst.add(pop());
}
.
.
.
Stack<Number> numberStack = ... ;
Collection<Object> objects = ... ;
numberStack.popAll(objects);
  • 위 또한 불공변이기 때문에 오류가 발생한다.
  • 입력 매개변수의 타입을 E 의 상위 타입인 Collection 으로 변경하면 된다.
public void popAll(Collectio <? super E> dst) {
    while(!isEmpty())
        dst.add(pop());
}

유연성을 극대화하기 위해서는 원소의 생성자나 소비자용 입력매개변수에 와일드카드를 사용하라. 한편 입력 매개변수가 생산자와 소비자의 역할을 동시에 한다면 와일드 카드를 써도 좋을 것이 없다.

PECS

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 은 언제나 소비자이기 때문에 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 API 라면 간단한 두 번째가 낫다. 어떤 리스트든 이 메소드에 넘기면 두 원소를 교환해준다. 신경 써야 할 타입 매개변수도 없다.
  • 메소드 선언에 타입 매개변수가 한 번만 나온다면 와일드 카드로 대체하라

하지만 두 번째 코드에는 문제가 있다. 다음의 코드는 컴파일되지 않는다.

public static void swap(List<?> list, int i, int j) {
    list.set(i, list.set(j, list.get(i)));
}
  • List<?> 에는 null 외에 아무것도 넣을 수 없다는데 원인이 있다.
  • private helper method를 사용하면 이를 해결할 수 있다.
  • 실제 타입을 알기 위해선 helper method 는 제네릭 메소드여야 한다.
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)));
}
  • 다소 복잡하지만 이런 과정을 통해 클라이언트는 와일드카드 기반의 선언을 유지하며 사용할 수 있다.
profile
내 머릿속 지우개

0개의 댓글