Java 제네릭(Generics)은 컴파일 시 타입 안전성을 보장하면서 코드를 재사용할 수 있게 한다. 와일드카드와 PECS 원칙은 제네릭을 유연하게 사용하는 핵심 개념이다.
타입 소거(Type Erasure)
java
// 컴파일 전
List<String> list = new ArrayList<String>();
// 컴파일 후 (타입 소거)
List list = new ArrayList();
// 런타임에는 제네릭 타입 정보가 없음
list instanceof List<String> // 컴파일 에러
list instanceof List // OK
와일드카드
java
// 비한정 와일드카드: 어떤 타입이든
void printList(List<?> list) {
for (Object o : list) System.out.println(o);
}
// 상한 와일드카드 (? extends T): T 또는 T의 서브타입
double sum(List<? extends Number> list) {
return list.stream().mapToDouble(Number::doubleValue).sum();
}
// 하한 와일드카드 (? super T): T 또는 T의 슈퍼타입
void addNumbers(List<? super Integer> list) {
list.add(1); list.add(2); list.add(3);
}
PECS 원칙
Producer Extends, Consumer Super
java
// Producer (읽기): ? extends T → 데이터를 꺼내서 사용
static <T> void copy(List<? extends T> src, // Producer
List<? super T> dest) { // Consumer
for (T item : src) dest.add(item);
}
List<Integer> ints = List.of(1, 2, 3);
List<Number> nums = new ArrayList<>();
copy(ints, nums); // Integer → Number (PECS 적용)
제네릭 메서드와 바운드
java
// 다중 바운드
<T extends Comparable<T> & Serializable> T max(T a, T b) {
return a.compareTo(b) >= 0 ? a : b;
}
// 재귀적 타입 바운드
<T extends Comparable<T>> T max(List<T> list) {
return list.stream().max(Comparator.naturalOrder()).orElseThrow();
}
제네릭 클래스 설계
java
public class Pair<A, B> {
private final A first;
private final B second;
public Pair(A first, B second) { this.first = first; this.second = second; }
public A getFirst() { return first; }
public B getSecond() { return second; }
public static <X, Y> Pair<X, Y> of(X x, Y y) { return new Pair<>(x, y); }
}
Pair<String, Integer> p = Pair.of("Alice", 30);
관련 개념