티스토리 뷰

728x90
반응형

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


  • 아이템 28  에서도 나온 얘기이지만 매개변수화 타입은 불공변입니다. 즉 서로 다른 타입 Type1과 Type2가 있을 때 List<Type1>은 List<Type2>의 하위 타입도 상위 타입도 아닙니다.
  • 조금 더 쉽게 표현하면 List<String>은 List<Object>의 하위타입도 아니고 상위 타입도 아닙니다. 그냥 타입이 다르며, List<Object>는 모든 것을 담을 수 있지만 List<String>은 문자열만 담을 수 있기에 List<String>은 List<Object>가 하는일을 제대로 수행하지 못합니다.

 

💡결함이 있는 메서드(컴파일 에러 발생)

  • Integer는 Number의 하위 타입이니 논리적으로는 문제가 없을거 같지만 실제로는 컴파일 에러가 발생합니다. 원인은 매개변수화 타입이 불공변이기 때문에 Number로 변환할 수 없다는 것입니다.
public class Stack<E> {

    private E[] elements;
    private int size;
    private static final int DEFAULT_INITIAL_CAPACITY = 16;

    public Stack() {
        this.elements = (E[]) new Object[DEFAULT_INITIAL_CAPACITY];
    }

    public void push(E o) {
        ensureCapacity();
        elements[size++] = o;
    }

    public E pop() {
        if (size == 0) {
            throw new EmptyStackException();
        }

        E result = elements[--size];
        elements[size] = null;
        return result;
    }

	// 예제 메서드
    public void pushAll(Iterable<E> src) {
        for(E e : src) push(e);
    }

    private void ensureCapacity() {
        if (elements.length == size) {
            elements = Arrays.copyOf(elements, 2 * size + 1);
        }
    }
}

public class EffectiveJavaApplication {

    public static void main(String[] args) throws Exception {
        Stack<Number> stack = new Stack<>();
        List<Integer> integers = Arrays.asList(1, 2, 3, 4, 5);
		
        // java: 호환되지 않는 유형: java.util.List<java.lang.Integer>
        // java.lang.Iterable<java.lang.Number>로 변환할 수 없습니다.
        stack.pushAll(integers);
    }
}

 

💡한정적 와일드 카드 타입을 적용한 메서드

  • 매개변수의 타입을 Iterable<? extends E>로 와일드 카드 타입을 적용하면 컴파일 에러가 사라집니다.
public void pushAll(Iterable<? extends E> src) {
    for(E e : src) push(e);
}

 

💡생산자 매개변수화 타입과 소비자 매개변수화 타입

  • 매개변수화 타입 E가 생산자라면 <? extends E>를 사용하고, 소비자라면 <? super E>를 사용합니다.
<? extends E> // 생산자
<? super E> // 소비자

 

💡생산자 매개변수화 타입과 소비자 매개변수화 타입 예제

  • 생산자는 extends로 하위 타입 유연성을 높이고, 소비자라면 super로 상위 타입 유연성을 높여야 합니다.
  • 참고: 반환타입은 한정적 와일드 카드 타입(Collection<? extends E>)을 사용하면 안됩니다.
    • 유연성을 높여주기는 커녕 클라이언트 코트에서도 와일드 카드 타입을 써야하기 때문입니다.
public class Stack<E> {

    private E[] elements;
    private int size;
    private static final int DEFAULT_INITIAL_CAPACITY = 16;

    public Stack() {
        this.elements = (E[]) new Object[DEFAULT_INITIAL_CAPACITY];
    }

    // 소비자 매개변수화 타입
    public Collection<E> popAll(Collection<? super E> o) {
        while (size != 0) {
            E result = elements[--size];
            elements[size] = null;
            o.add(result);
        }
        return (Collection<E>) o;
    }
	
    // 생산자 매개변수화 타입
    public void pushAll(Iterable<? extends E> src) {
        for(E e : src) push(e);
    }
}

public class EffectiveJavaApplication {

    public static void main(String[] args) throws Exception {
        Stack<Number> stack = new Stack<>();
        List<Integer> integers = Arrays.asList(1, 2, 3, 4, 5);

        // 생산자
        stack.pushAll(integers); 
        
        // 소비자
        stack.popAll(new ArrayList<>()).stream().forEach(System.out::print); // 54321
    }
}

 

💡정리

  • 와일드 카드 타입을 적용하면 API가 유연해집니다. 단, 타입을 정확히 지정해야 하는 상황에서는 와일드 카트 타입을 쓰지 말아야 합니다.
  • 생산자 매개변수화 타입이라면 <? extends E>
  • 소비자 매개변수화 타입이라면 <? super E>
  • Comparable과 Comparator는 모두 소비자입니다. Comparable<? super E>
  • 메서드 선언 타입에 타입 매개변수가 한 번만 등장하면 와일드 카드를 사용하는게 좋습니다.

 

 

 

728x90
반응형