ch11_제네릭
통합 문서입니다.
1. 제네릭
제네릭 (Generics)
학습 목표
- 제네릭이 제공하는 타입 안정성과 재사용성의 의미를 이해할 수 있다.
- 제네릭 클래스/메소드, 경계 타입, 와일드카드(PECS)를 실무 코드에 적용할 수 있다.
- 타입 소거(Type Erasure) 제약을 이해하고 우회 설계를 할 수 있다.
1. 제네릭이 필요한 이유
제네릭 도입 전에는 컬렉션에서 Object를 사용해 다음 문제가 있었다.
- 런타임 캐스팅 오류 위험
- 불필요한 형변환 코드
- API 사용 의도 불명확
제네릭은 타입 정보를 컴파일 시점에 반영해 오류를 조기에 잡는다.
List<String> names = new ArrayList<>();
names.add("Kim");
// names.add(1); // 컴파일 오류
2. 기본 문법
2.1 제네릭 클래스
class Box<T> {
private T value;
public void set(T value) { this.value = value; }
public T get() { return value; }
}
사용:
Box<Integer> b = new Box<>();
b.set(10);
int n = b.get();
2.2 제네릭 메소드
public static <T> void swap(List<T> list, int i, int j) {
T tmp = list.get(i);
list.set(i, list.get(j));
list.set(j, tmp);
}
3. 타입 파라미터 관례
자주 쓰는 이름:
T: TypeE: ElementK,V: Key, ValueR: Return type
이름 자체는 자유지만 관례를 지키면 가독성이 좋아진다.
4. 경계 타입(Bounded Type Parameter)
숫자 타입만 허용하는 평균 계산:
public static <T extends Number> double avg(List<T> list) {
return list.stream().mapToDouble(Number::doubleValue).average().orElse(0.0);
}
장점:
- API 계약이 명확
- 잘못된 타입 입력을 컴파일 단계에서 차단
5. 와일드카드와 PECS
와일드카드:
<?>: 어떤 타입이든<? extends T>: T 또는 하위 타입(Producer)<? super T>: T 또는 상위 타입(Consumer)
PECS 원칙:
- Producer Extends
- Consumer Super
예:
void printAll(List<? extends Number> src) { ... } // 읽기 중심
void addInts(List<? super Integer> dst) { ... } // 쓰기 중심
6. 타입 소거(Type Erasure)
Java 제네릭은 런타임에 타입 파라미터 정보 대부분이 제거된다.
제약:
new T()불가T.class불가instanceof List<String>불가
우회:
Class<T>타입 토큰 전달- 팩토리/전략 인터페이스 활용
7. 제네릭과 배열의 차이
배열은 공변(covariant), 제네릭은 불공변(invariant) 기본.
Object[] arr = new String[2]; // 가능(런타임 오류 가능성 존재)
// List<Object> list = new ArrayList<String>(); // 불가
제네릭의 불공변성은 타입 안전성을 높이기 위한 설계다.
8. 오토박싱과 성능
제네릭은 primitive를 직접 다룰 수 없어 wrapper 타입 사용이 필요하다.
List<Integer> nums = new ArrayList<>();
대량 연산에서는 오토박싱 비용이 생길 수 있으므로
primitive stream(IntStream) 등 대안을 검토한다.
9. 실무 설계 포인트
- API 반환/매개변수에 구체 구현 대신 인터페이스 타입 사용
- 읽기 전용/쓰기 전용 의도는 와일드카드로 명확화
- raw type(
List) 사용 금지 @SuppressWarnings("unchecked")는 최소 범위에서만 사용
10. 자주 하는 실수
Listraw type 사용extends/super방향 혼동- 제네릭 캐스팅 경고 무시
- 타입 소거 제약을 모르고 런타임 타입 검사 시도
11. 정리
- 제네릭의 핵심은 재사용보다 타입 안정성이다.
- 와일드카드(PECS)와 타입 소거 제약을 이해하면 안전한 API 설계가 가능하다.
- 제네릭은 컬렉션, 스트림, 함수형 API 전반의 기반이 되는 문법이다.
2. 문제
문제
ch11 범위(제네릭 클래스/메소드, 경계 타입, 와일드카드, 타입 소거) 문제입니다.
A. 제네릭 기초
Box<T>클래스를 작성하고set/get을 구현하시오.Box<String>,Box<Integer>를 각각 사용하시오.- raw type(
Box box) 사용 시 경고가 발생하는 이유를 설명하시오.
B. 제네릭 메소드
- 배열/리스트의 두 원소를 교환하는 제네릭
swap메소드를 작성하시오. - 두 값을 받아 더 큰 값을 반환하는 제네릭 메소드(
T extends Comparable<T>)를 작성하시오.
C. 경계 타입
Number상한 제네릭으로 숫자 리스트 평균을 계산하시오.Comparable경계를 사용해 최솟값/최댓값 유틸 메소드를 작성하시오.
D. 와일드카드(PECS)
List<? extends Number>를 입력받아 출력만 하는 메소드를 작성하시오.List<? super Integer>에 값을 추가하는 메소드를 작성하시오.extends목록에add가 왜 제한되는지 설명하시오.
E. 타입 소거 제약
new T()가 불가능한 예제를 만들고 이유를 설명하시오.Class<T>를 받아 객체 생성/검증하는 방식으로 우회 코드를 작성하시오.instanceof List<String>이 왜 불가능한지 설명하시오.
F. 챌린지
- 제네릭 캐시 클래스
Cache<K, V>를 작성하시오. - 만료 정책 인터페이스를 제네릭으로 분리하시오.
- 잘못된 타입 사용이 컴파일 단계에서 차단되는 테스트 코드를 작성하시오.
제출 체크리스트
- raw type을 사용하지 않았는가?
- 와일드카드 방향(extends/super)이 의도와 맞는가?
- 타입 소거 제약을 우회할 때 안전성을 보장했는가?