이펙티브 자바/5장 제네릭

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

말랑공룡 2024. 5. 19. 21:48
// E 생산자(producer) 매개변수에 와일드카드 타입 적용
public void pushAll(Iterable<? extends E> src) {
	for (E e : src) push(e);
}


// E 소비자(consumer) 매개변수에 와일드카드 타입 적용
public void popAll(Collection(? super E) dst) {
	while (!isEmpty()) dst.add(pop());
}

 

Stack<E> 클래스의 메소드를 한정적 와일드카드를 사용해 구현했다.

간단한 예제지만, 헷갈리는 부분들을 분명하게 공부하고 넘어가고자 한다.

 

제네릭에서의 생산자(producer)와 소비자(consumer)


생산자(producer)

 

생산자는 데이터를 "생산"하여 제공하는 역할을 한다. 즉, 컬렉션이나 객체로부터 데이터를 추출하거나 가져오는 경우를 말한다.

? extends E 와일드카드를 사용하여 생산자를 나타낸다. 이 와일드카드는 컬렉션이나 객체에서 데이터를 읽을 때 타입 안전성을 제공한다.

주요 특징:

  • 데이터를 제공함 (읽기).
  • E의 하위 타입을 허용.
public static void readFromList(List<? extends Number> list) {
    for (Number number : list) {
        System.out.println(number);
    }
}

List<Integer> integers = Arrays.asList(1, 2, 3);
List<Double> doubles = Arrays.asList(1.1, 2.2, 3.3);

readFromList(integers); // 가능
readFromList(doubles);  // 가능

 

위 예제에서 readFromList 메서드는 Number의 하위 타입인 Integer와 Double 리스트를 읽어들여 값을 출력한다. 리스트에 새로운 요소를 추가하는 것은 허용되지 않는다.

 

소비자(consumer)

 

소비자는 데이터를 "소비"하여 사용하는 역할을 한다. 즉, 컬렉션이나 객체에 데이터를 삽입하거나 설정하는 경우를 말한다.

? super E 와일드카드를 사용하여 소비자를 나타낸다. 이 와일드카드는 컬렉션이나 객체에 데이터를 쓰는 동안 타입 안전성을 제공한다.

주요 특징:

  • 데이터를 소비함 (쓰기).
  • E의 상위 타입을 허용.

예시:

public static void addToList(List<? super Integer> list) {
    list.add(1);
    list.add(2);
}

List<Number> numbers = new ArrayList<>();
List<Object> objects = new ArrayList<>();

addToList(numbers); // 가능
addToList(objects); // 가능

 

위 예제에서 addToList 메서드는 Integer의 상위 타입인 NumberObject 리스트에 Integer 값을 추가할 수 있다. 리스트에서 요소를 읽을 때는 Object 타입으로 반환된다.

 

PECS 원칙 (Producer Extends, Consumer Super)

이 원칙은 생산자와 소비자를 이해하는 데 도움이 된다:

  • Producer Extends: 생산자는 ? extends E를 사용.
  • Consumer Super: 소비자는 ? super E를 사용.
import java.util.Arrays;
import java.util.List;
import java.util.ArrayList;

public class Main {
    public static void main(String[] args) {
        List<Integer> integers = Arrays.asList(1, 2, 3);
        List<Double> doubles = Arrays.asList(1.1, 2.2, 3.3);

        // 생산자: 리스트에서 읽기
        readFromList(integers);
        readFromList(doubles);

        List<Number> numbers = new ArrayList<>();
        List<Object> objects = new ArrayList<>();

        // 소비자: 리스트에 쓰기
        addToList(numbers);
        addToList(objects);
    }

    // 생산자: 데이터 제공 (읽기)
    public static void readFromList(List<? extends Number> list) {
        for (Number number : list) {
            System.out.println(number);
        }
    }

    // 소비자: 데이터 소비 (쓰기)
    public static void addToList(List<? super Integer> list) {
        list.add(1);
        list.add(2);
    }
}

 

요약

  • 생산자 (Producer): 데이터를 제공하는 역할. ? extends E 사용. 주로 읽기 작업에 사용.
  • 소비자 (Consumer): 데이터를 소비하는 역할. ? super E 사용. 주로 쓰기 작업에 사용.

이 개념을 통해 제네릭 타입을 사용할 때 타입 안전성을 높이고 유연성을 유지할 수 있다.