제네릭 와일드카드 완전정복: PECS(Producer Extends, Consumer Super) 암기팁
제네릭을 쓰다 보면 List<? extends T> 과 List<? super T> 중 무엇을 골라야 할지 헷갈립니다.
이 글은 실무에서 바로 써먹을 수 있도록 “언제 어떤 와일드카드를 쓰는지”를 예제 코드와 함께 정리했습니다.
핵심은 간단합니다. PECS — Producer Extends, Consumer Super.
? extends,? 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 Number는 Integer, 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 Integer는 Integer 또는 그 상위 타입(Number, Object)의 리스트에 Integer 값을 안전하게 추가할 때 유용합니다. 반대로 읽기는 Object로만 안전합니다.
3) 흔한 오류 — 컴파일 에러로 배우기
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) 한 눈에 보는 표(직접 제작)

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 검사를 하거나 설계를 조정하세요.
⏩ 다음 글 보기: Stream API: for→stream 리팩터링 10가지(성능/가독 균형)
👉 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 스레드풀 사이즈/큐 전략
'Java & Spring' 카테고리의 다른 글
| Java Record vs Lombok DTO 선택 기준: 언제 무엇을 쓸까 (0) | 2025.09.28 |
|---|---|
| java.time 제대로 쓰기: 타임존/포맷 실수 7가지와 안전한 사용법 (0) | 2025.09.28 |
| Optional.orElse vs orElseGet 차이와 NPE 방지: 실무 규칙 7가지 (0) | 2025.09.28 |
| Stream API: for→stream 리팩터링 10가지(성능/가독 균형) (0) | 2025.09.28 |
| ArrayList vs LinkedList: 언제 무엇을 써야하나 (+O(1) 착각 5가지) (0) | 2025.09.27 |