본문 바로가기
Java & Spring

제네릭 와일드카드 완벽 마스터: PECS(Producer Extends, Consumer Super) 암기팁

by yamoojin83 2025. 9. 28.

제네릭 와일드카드 완전정복: PECS(Producer Extends, Consumer Super) 암기팁

제네릭을 쓰다 보면 List<? extends T>List<? super T> 중 무엇을 골라야 할지 헷갈립니다.

이 글은 실무에서 바로 써먹을 수 있도록 “언제 어떤 와일드카드를 쓰는지”를 예제 코드와 함께 정리했습니다.

핵심은 간단합니다. PECSProducer Extends, Consumer Super.

 

PECS 한 줄 암기: 데이터를 “꺼내서(produce) 읽기”만 한다면 ? extends,
“넣어서(consume) 쓰기”가 목적이라면 ? super.
 

1) 왜 헷갈릴까? — 공변/반공변/불공변 한 번에 정리

  • 배열String[]Object[]는 허용(공변)이라 런타임 오류로 이어질 수 있음.
  • 제네릭은 기본적으로 불공변. List<String>List<Object>의 하위 타입이 아님.
  • 대신 와일드카드로 “읽기 전용 상한(extends)”과 “쓰기 허용 하한(super)”을 안전하게 표현한다.

 

2) 올바른 사용 — 예제로 보는 PECS

2-1. Producer: ? extends — 읽기 위주

static double sum(List<? extends Number> nums) {
  double s = 0;
  for (Number n : nums) s += n.doubleValue();
  return s;
}

// 사용
List<Integer> ints = List.of(1,2,3);
List<Double>  dbls = List.of(1.5,2.5);
double a = sum(ints);
double b = sum(dbls);
  

? extends NumberInteger, Double 등 어떤 하위 타입 리스트든 받습니다. 단, 추가(add)는 금지됩니다. 컴파일러는 정확한 구체 타입을 몰라서 타입 안전을 보장할 수 없기 때문입니다.

2-2. Consumer: ? super — 쓰기 위주

static void addIntegers(List<? super Integer> out) {
  out.add(1);
  out.add(2);
}

// 사용
List<Number> nums = new ArrayList<>();
addIntegers(nums); // OK (Number는 Integer의 상위)
  

? super IntegerInteger 또는 그 상위 타입(Number, Object)의 리스트에 Integer 값을 안전하게 추가할 때 유용합니다. 반대로 읽기Object로만 안전합니다.

 

3) 흔한 오류 — 컴파일 에러로 배우기

오류 1: List<? extends Number>에 값을 넣으려 함 → 컴파일 에러
오류 2: List<Object>List<String>의 상위라고 착각 → 제네릭은 불공변
오류 3: 복사/머지 함수에서 매개변수 타입을 모두 List<T>로 고정 → 활용성이 떨어짐(와일드카드로 풀어야 함)
// 오류 예시
void wrong(List<? extends Number> xs) {
  // xs.add(10); // 컴파일 에러: capture of ? extends Number
}
  

4) 복사/합치기 — 와일드카드와 타입 파라미터의 조합

표준 패턴은 “소스는 ? extends T, 타깃은 ? super T”입니다.

static <T> void copy(List<? extends T> src, List<? super T> dst) {
  for (T e : src) dst.add(e);
}

// 예시
List<Integer> src = List.of(1,2,3);
List<Number>  dst = new ArrayList<>();
copy(src, dst); // OK
  

5) 언제 와일드카드, 언제 타입 파라미터?

  • 읽기 전용으로 다양한 하위 타입을 받고 싶다 → ? extends
  • 쓰기를 허용하면서 상위 타입 컨테이너에 넣고 싶다 → ? super
  • 두 방향(읽기+쓰기)을 모두 강하게 제약하고 싶다 → <T> 타입 파라미터로 List<T>
 

6) 한 눈에 보는 표(직접 제작)

 

PECS table: extends vs super
PECS 표 요약: 읽기는 extends, 쓰기는 super. 사진은 설명을 위한 예시 이미지입니다.

 

7) 요약

  • PECS: Producer Extends, Consumer Super.
  • ? extends는 읽기만, ? super는 쓰기만 안전하다.
  • 복사/변환 유틸리티는 copy(src, dst)처럼 <T> + extends/super를 조합.

 

FAQ

Q1. List<? extends Number>에서 안전하게 꺼낸 값을 Double로 바로 쓸 수 있나요?
A. 컴파일러는 정확한 구체 타입을 모릅니다. 공통 상위인 Number로 받아 doubleValue() 같은 메서드를 쓰세요.

Q2. List<? super Integer>에서 읽을 땐 어떻게 하나요?
A. 안전한 타입은 Object뿐입니다. 필요한 타입으로 쓰려면 적절히 instanceof 검사를 하거나 설계를 조정하세요.

 

 

👉 1편: ArrayList vs LinkedList: 언제 무엇을 쓰나 (+O(1) 착각 5가지)

👉 2편: 제네릭 와일드카드 완전정복(PECS 암기팁 포함)

👉 3편: Stream API: for→stream 리팩터링 10가지(성능/가독 균형)

👉 4편: Optional.orElse vs orElseGet 차이와 NPE 방지

👉 5편: java.time 제대로 쓰기(타임존/포맷 실수 7가지)

👉 6편: Java Record vs Lombok DTO 선택 기준

👉 7편: NIO.2로 폴더 스캔/감시 구현(FileVisitor/WatchService)

👉 8편: ExecutorService 스레드풀 사이즈/큐 전략

👉 9편: CompletableFuture allOf/anyOf로 외부 API 병렬화

👉 10편: 예외 처리 베스트 프랙티스(체크/언체크, 경계 설계)