본문 바로가기

STUDY/이펙티브자바

5-5) 한정적 와일드카드를 사용해 유연성을 높혀라

매개변수화 타입의 불공변성

 

// Integer 타입은 Number 타입을 상속했기때문에, num = intNum가 가능하다.
Number num = 1;
Integer intNum = 1;
num = intNum;

// Integer 타입은 Number 타입을 상속했지만, 매개변수 타입으로 전달되었을때는 그 관계가 유지되지 않는다.
// 따라서 numStack = intStack 코드는 컴파일 에러가 발생한다.
Stack<Number> numStack = new Stack<>();
Stack<Integer> intStack = new Stack<>();
numStack = intStack;

매개변수화 타입은 불공변이다. 불공변이라는 말은 타입의 관계가 같이 변하지 않는다는 것을 말한다. 위의 코드를 보면 Integer 타입은 Number 타입을 상속하였기때문에 두 타입은 부모/자식의 관계가 성립한다. 하지만 두타입이 매개변수 타입으로 사용된 Stack<Number>와 Stack<Integer>는 부모/자식 관계가 성립하지 않는다.

 

public class Stack<T> {
    ...
    // 여러 원소들을 스택에 넣는 pushAll 메소드 추가
    public void pushAll(Iterable<T> src) {
        for(T e : src) {
            this.push(e);
        }
    }
    ...
}

 

List<Number> nums = new ArrayList<>();
List<Integer> ints = new ArrayList<>();

Stack<Number> numStack = new Stack<>();
numStack.pushAll(nums);
// List<Integer> 타입은 Iterable<Number>와 관계가 없기때문에 컴파일 에러 발생
numStack.pushAll(ints);

매개변수 타입의 불공변성은 유연한 코드 작성을 방해한다. 여러 원소를 한번에 넣기위해 pushAll 함수를 만들었지만 Number의 하위 타입인 Integer를 원소로 가지고있는 List<Integer> 객체를 넣으려고 하니 컴파일 에러가 발생하였다. push 메소드에는 Integer 타입을 넣을 수 있는걸 생각하면 웃긴 일이다.

 

 


생산자 매개변수에 와일드 카드 적용

 

public class Stack<T> {
    ...
    // 여러 원소들을 스택에 넣는 pushAll 메소드 추가
    public void pushAll(Iterable<? extends T> src) {
        for(T e : src) {
            this.push(e);
        }
    }
    ...
}

 

List<Number> nums = new ArrayList<>();
List<Integer> ints = new ArrayList<>();

Stack<Number> numStack = new Stack<>();
numStack.pushAll(nums);
numStack.pushAll(ints);

와일드 카드를 사용하면 유연한 코드를 작성할 수 있다. 기존 pushAll 메소드에서 매개변수의 타입을 Iterable<? extends T>로 바꾸었다. 이는 T 타입을 상속하는 타입 매개변수를 가지는 Iterable까지 매개변수로 받겠다는 의미이다. 이제 Stack<Number>의 pushAll메소드에 List<Integer>를 넣을 수 있다.

 

 


소비자 매개변수에 와일드카드 적용

 

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

 

// Integer의 부모관계인 Number 타입의 List로 popAll을 수행하였다.
Stack<Integer> intStack = new Stack<>();
List<Number> nums = new ArrayList<>();
intStack.popAll(nums);

Stack의 모든 요소를 꺼내는 popAll 메소드 역시 와일드 카드를 사용하여 유연한 코드를 작성하였다. Collection<? super T>는 T 타입의 부모를 타입매개변수로 가지는 Collection도 매개변수로 받겠다는 의미이다. 이 덕분에 Stack<Integer>의 popAll 매개변수로 List<Number> 타입을 전달할 수 있다.

 

 


의외의 다양한 와일드카드 활용

 

// 생성자
public Chooser(Collection<? extends T> choices) {
    choiceList = new ArrayList<>(choices);
}

 

// union 메소드
public static <E> Set<E> union(Set<? extends E> s1, Set<? extends E> s2) {
    Set<E> result = new HashSet<E>(s1);
    result.addAll(s2);
    return result;
}

 

// 재귀적 타입 한정에도 와일드카드를 사용하였다.
public static <E extends Comparable<? super E>> E max(List<? extends E> list) {
    if (list.isEmpty())
        throw new IllegalArgumentException("Empty list");

    E result = null;
    for (E e : list)
        if (result == null || e.compareTo(result) > 0)
            result = e;

    return result;
}

 

728x90