관리 메뉴

루시와 프로그래밍 이야기

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

스터디/이펙티브 자바

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

Lucy_Ko 2022. 10. 13. 00:55
  • 매개변수화 타입은 불공변(invariant)이다.
  • 서로 다른 타입 Type1과 Type2가 있을 때 List<Type1>은 List<Type2>의 하위 타입도 상위 타입도 아니다.
  • List<String>은 List<Object>가 하는 일을 제대로 수행하지 못하니 하위 타입이 될 수 없다.
    (리스코프 치환 원칙에 어긋난다.) ITEM10
  • 때로는 불공변 방식보다 유연한 방식이 필요하다.

Stack의 API

public class Stack<E> {
    public Stack();
    public void push(E e);
    public E pop();
    public boolean isEmpty();
}

여기에 일련의 원소를 스택에 넣는 메서드를 추가

public class Stack<E> {
    public void pushAll(Iterable<E> src) {
        for (E e : src) {
            push(e);
        }
    }
}

 

Integer 는 Number 의 하위 타입이니 pushAll(Integer타입값)이 잘 동작할 것 같다.

import java.util.Stack;

class Example {
    public static void main(String[] args) {
        Stack<Number> numberStack = new Stack<>();
        Iterable<Integer> integers;
        numberStack.pushAll(integers);
    }
}
  • 실제 매개 변수화 타입이 불공변이기 때문에 오류 메시지를 내뱉는다.
  • 자바는 이런 상황에 대처할 수 있는 한정적 와일드카드 타입이라는 특별한 매개변수화 타입을 지원한다.

와일드카드 타입 적용

class Example {
    public void pushAll(Itrable<? extends E> src) {
        for (E e : src) {
            push(e);
        }
    }
}
  • 유연성을 극대화하려면 원소의 생산자나 소비자용 입력 매개변수에 와일드카드 타입을 사용해야 한다.
  • 입력 매개변수가 생산자와 소비자 역할을 동시에 한다면 와일드 카드 타입을 써도 좋을 게 없다.
  • 타입을 정확히 지정해야 하는 상황으로, 이때는 와일드 카드 타입을 쓰지 않아야 한다.

펙스(PECS): producer-extends, consumer-super

  • 와일드카드 타입을 써야하는지에 대한 상황을 분별할 수 있는 기준
  • 매개변수화 타입 T가 생상자인경우 <? extends T>를 사용하고, 소비자인 경우 <? super T>를 사용해야 한다.
  • Stack 예
    • pushAll의 src 매개변수는 Stack이 사용할 E 인스턴스를 생산하므로 src의 적절한 타입은 Iterable<? extends E>이다.
       
    • popAll의 dst 매개변수는 Stack으로부터 E 인스턴스를 소비(RETURN 반환)하므로 dst의 적절한 타입은 Collection<? super E> 이다.
  • PECS 공식은 와일드카드 타입을 사용하는 기본원칙
 

ITEM30 의 union 코드 수정

class Example {
    public static <E> Set<E> union(Set<? extends E> si, Set<? extends E> s2) {
        Set<E> result = new HashSet<>(sl);
        result.addAll(s2);
        return result;
    }
}

class Client {
    public static void main(String[] args) {
        Set<Integer> integers = Set.of(1, 3, 5);
        Set<Double> doubles = Set.of(2.0, 4.0, 6.0);
        Set<Number> numbers = union(integers, doubles);
    }
}

정리

  • 조금 복잡하더라도 와일드 카드 타입을 적용하면 API가 훨씬 유연해진다.
  • 널리 쓰일 라이브러리를 작성한다면 반드시 와일드카드 타입을 적절히 사용해야 한다.
  • PECS 공식을 기억하자
    • 생산자(producer)는 extends를 소비자(consumer)는 super를 사용한다.
    • Comparable과 Comparator는 모두 소비자라는 사실을 잊지 않아야 한다.
Comments