아이템 28. 한정된 와일드카드를 써서 API유연성을 높여라.

28. 한정된 와일드카드를 써서 API유연성을 높여라.

형인자 자료형(parameterized type)은 불변(invariant)자료형이다. 다시 말해 type1과 type2가 있을때, List과 List사이에는 어떤 상-하위 자료형 관계도 성립 할수 없다. 즉, ```List```는 ```List```의 하위 자료형이 아니다. ```List```에는 어떤 객체도 넣을 수 있지만, ```List```에는 문자열만 넣을 수 있다.

Stack<Number> numberStack = new Stack<>();
Iterable<Integer> integers = ...;
numberStack.pushAll(integers);

List<Number>List<Integer> 간에는 부모-자식 관계가 성리될 수 없다. 즉 캐스팅이 불가능하다. 하지만 논리적(상식적)으로 두 Collection 간 pushAll과 같은 메소드는 호출 가능해야 할 것 같다.

위와 같은 문제를 해결하고자 한정적 와일드카드 자료형(bounded wildcard type) 이 존재한다.

Integer클래스는 Number에 의해서 상속된(extends) 구현체 이므로, 아래와 같이 구현하면 정상적으로 동작한다.

// E 객체 생산자 역할을 하는 인자에 대한 와일드 카드 자료형
public void pushAll(Iterable<? extends E> src) {
    for (E e : src)
        push(e);
}
Stack<Number> numberStack = new Stack<>();
Collection<Object> objects = ...;
numberStack.popAll(objects);

위와 같이 구현하면 Collection가 Collection의 하위 자료형이 아니라는 오류가 발생한다. 아래와 같이, E의 컬렉션이 아니라, E의 상위 자료형의 컬렉션이라고 명시하면 된다.

// E의 소비자 구실을 하는 인자에 대한 와일드카드 자료형
public void popAll(Collection<? super E> dst) {
    while(!isEmpty())
        dst.add(pop());
}

이와 같이 유연성을 극대화 하려면, 객체 생산자(producer)나 소비자(consumer)구실을 하는 메서드 인자의 자료형은 와일드 카드 자료형으로 구성해야 된다.

어떤 와일드 카드를 쓸지 어렵다면, 아래와 같은 약어를 외워 두자.

PECS( Produce - Extends, Consumer - Super)

[주의] 반환값에는 와일드카드 자료형을 쓰면 안된다.

좀더 유연한 코드를 만들 수 있기는 거켱, 클라이언트 코드 안에도 와일드 카드 자료형을 명시해야 하기 때문이다.

명시적 형인자(explicit type parameter)

Set<Integer> integers = ...;
Set<Double> doubles = ...;
Set<Number> numbers = Union.union(integers, double);
Union.java:14: incompatible types
found : Set<Number & Comparable<? extends Number & Comparable<?>>>
required: Set<Number>
    Set<Number> numbers = union(integers, doubles);
                                ^

위의 문제 해결은 __명시적 형인자__를 사용하여, 아래와 같이 union메서드에 정의함

// 명시적 형인자 ".<Number>" 적용
Set<Number> numbers = Union.<Number>union(integers, doubles);

비한정 와일드 카드 자료형

public static void swap(List<?> list, int i, int j){
    list.set(i, list.set(j, list.get(i)));
}

컴파일 오류 발생함, <?>이기 때문에, null이외에는 값을 넣을수 가 없음 이 문제를 해결하려면, 명시적으로 타입을 지정해 주거나, private Helper method를 정의하여 포착(capture)할 수 있게 해줘야함

public static void swap(List<?> list, int i, int j){
    swapHelper(list, i, j);
}
// 와일드 카드자료형을 포착하기 위한 private helper method
private static <E> void swapHelper(List<E> list, int i, int j){
    list.set(i, list.set(j, list.get(i)));
}

요약