ch11_제네릭

통합 문서입니다.


1. 제네릭

제네릭 (Generics)

학습 목표


1. 제네릭이 필요한 이유

제네릭 도입 전에는 컬렉션에서 Object를 사용해 다음 문제가 있었다.

  1. 런타임 캐스팅 오류 위험
  2. 불필요한 형변환 코드
  3. API 사용 의도 불명확

제네릭은 타입 정보를 컴파일 시점에 반영해 오류를 조기에 잡는다.

List<String> names = new ArrayList<>();
names.add("Kim");
// names.add(1); // 컴파일 오류

제네릭 컴파일타임 vs 런타임


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. 타입 파라미터 관례

자주 쓰는 이름:

이름 자체는 자유지만 관례를 지키면 가독성이 좋아진다.


4. 경계 타입(Bounded Type Parameter)

숫자 타입만 허용하는 평균 계산:

public static <T extends Number> double avg(List<T> list) {
    return list.stream().mapToDouble(Number::doubleValue).average().orElse(0.0);
}

장점:


5. 와일드카드와 PECS

와일드카드:

PECS 원칙:

예:

void printAll(List<? extends Number> src) { ... } // 읽기 중심
void addInts(List<? super Integer> dst) { ... }   // 쓰기 중심

6. 타입 소거(Type Erasure)

Java 제네릭은 런타임에 타입 파라미터 정보 대부분이 제거된다.

제약:

  1. new T() 불가
  2. T.class 불가
  3. instanceof List<String> 불가

우회:


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. 실무 설계 포인트

  1. API 반환/매개변수에 구체 구현 대신 인터페이스 타입 사용
  2. 읽기 전용/쓰기 전용 의도는 와일드카드로 명확화
  3. raw type(List) 사용 금지
  4. @SuppressWarnings("unchecked")는 최소 범위에서만 사용

10. 자주 하는 실수

  1. List raw type 사용
  2. extends/super 방향 혼동
  3. 제네릭 캐스팅 경고 무시
  4. 타입 소거 제약을 모르고 런타임 타입 검사 시도

11. 정리


2. 문제

문제

ch11 범위(제네릭 클래스/메소드, 경계 타입, 와일드카드, 타입 소거) 문제입니다.


A. 제네릭 기초

  1. Box<T> 클래스를 작성하고 set/get을 구현하시오.
  2. Box<String>, Box<Integer>를 각각 사용하시오.
  3. raw type(Box box) 사용 시 경고가 발생하는 이유를 설명하시오.

B. 제네릭 메소드

  1. 배열/리스트의 두 원소를 교환하는 제네릭 swap 메소드를 작성하시오.
  2. 두 값을 받아 더 큰 값을 반환하는 제네릭 메소드(T extends Comparable<T>)를 작성하시오.

C. 경계 타입

  1. Number 상한 제네릭으로 숫자 리스트 평균을 계산하시오.
  2. Comparable 경계를 사용해 최솟값/최댓값 유틸 메소드를 작성하시오.

D. 와일드카드(PECS)

  1. List<? extends Number>를 입력받아 출력만 하는 메소드를 작성하시오.
  2. List<? super Integer>에 값을 추가하는 메소드를 작성하시오.
  3. extends 목록에 add가 왜 제한되는지 설명하시오.

E. 타입 소거 제약

  1. new T()가 불가능한 예제를 만들고 이유를 설명하시오.
  2. Class<T>를 받아 객체 생성/검증하는 방식으로 우회 코드를 작성하시오.
  3. instanceof List<String>이 왜 불가능한지 설명하시오.

F. 챌린지

  1. 제네릭 캐시 클래스 Cache<K, V>를 작성하시오.
  2. 만료 정책 인터페이스를 제네릭으로 분리하시오.
  3. 잘못된 타입 사용이 컴파일 단계에서 차단되는 테스트 코드를 작성하시오.

제출 체크리스트

  1. raw type을 사용하지 않았는가?
  2. 와일드카드 방향(extends/super)이 의도와 맞는가?
  3. 타입 소거 제약을 우회할 때 안전성을 보장했는가?