1. 개요

List<? extends Number> 와 List<? super Number>, 처음 보면 둘 다 그냥 “숫자 리스트” 처럼 보이지만 하나는 읽기 전용이고 하나는 쓰기가 된다. 이 글은 제네릭 와일드카드가 왜 필요한지, PECS 원칙으로 언제 뭘 쓰는지 정리한다.


2. 핵심 내용

2-1. 제네릭이 없던 시절

Java 5 이전에는 컬렉션이 전부 Object 를 담았다. 꺼낼 때마다 캐스팅이 필요했고, 엉뚱한 타입을 넣어도 컴파일 시점엔 안 걸렸다. 제네릭은 이 문제를 컴파일 시점으로 끌어온다. List<String> 에 Integer 를 넣으려 하면 그 자리에서 컴파일 에러가 난다.

2-2. 제네릭은 불변(invariant)이다

Integer 는 Number 의 하위 타입이지만, List<Integer> 는 List<Number> 의 하위 타입이 아니다.

List<Number> numbers = new ArrayList<Integer>(); // 컴파일 에러

이유는 만약 허용된다면 numbers.add(3.14) 로 Double 을 넣을 수 있게 되는데, 실제로는 Integer 리스트이므로 런타임에 깨진다. 이걸 막으려고 제네릭은 기본적으로 불변이다. 하지만 “숫자 종류라면 뭐든 읽기만 할 건데” 같은 상황에서는 이 불변성이 너무 빡빡하다. 여기서 와일드카드가 필요해진다.

2-3. PECS: Producer Extends, Consumer Super

Joshua Bloch 가 정리한 원칙이다. 해당 타입 파라미터가 데이터를 꺼내기만(생산) 한다면 extends, 데이터를 넣기만(소비) 한다면 super 를 쓴다.

// Producer: 꺼내서 합산만 함 -> extends
static double sum(List<? extends Number> list) {
    double total = 0;
    for (Number n : list) total += n.doubleValue();
    return total;
}
 
// Consumer: 값을 넣기만 함 -> super
static void addIntegers(List<? super Integer> list) {
    list.add(1);
    list.add(2);
}
 
sum(List.of(1, 2, 3));        // List<Integer> 도 허용
sum(List.of(1.1, 2.2));       // List<Double> 도 허용
 
List<Number> nums = new ArrayList<>();
addIntegers(nums); // List<Number> 는 List<? super Integer> 를 만족

sum 은 리스트 안 값을 읽기만 하므로 ? extends Number 로 Integer, Double 리스트를 모두 받을 수 있다. addIntegers 는 Integer 를 넣기만 하므로 ? super Integer 로 Integer, Number, Object 리스트를 모두 받을 수 있다.

2-4. ? extends T 에는 왜 add 가 안 되나

List<? extends Number> list = new ArrayList<Integer>();
list.add(1); // 컴파일 에러

컴파일러 입장에서 list 의 실제 타입은 Integer 일 수도 Double 일 수도 있다. 어떤 타입인지 모르니 아무 값도 안전하게 넣을 수 없다(null 만 예외). 그래서 extends 는 사실상 읽기 전용이 된다. 이 원칙은 컬렉션 프레임워크 의 Collections.copy() 같은 유틸리티 메서드 시그니처에서도 그대로 쓰인다.


3. 마무리

요약

  • 제네릭은 불변이다. List<Integer> 는 List<Number> 의 하위 타입이 아니다.
  • 값을 꺼내서 쓰기만 하면(Producer) ? extends T, 값을 넣기만 하면(Consumer) ? super T.
  • ? extends T 는 실제 타입을 알 수 없어 null 외의 값을 넣을 수 없다(사실상 읽기 전용).
  • PECS 원칙은 라이브러리 유틸리티 메서드 시그니처를 이해하는 데도 그대로 적용된다.