제네릭과 와일드카드
제네릭(Generics) — 타입 안전성을 컴파일 타임으로 끌어올리기
제네릭이 왜 필요한지, 타입 소거가 무엇을 가능하게 하고 무엇을 막는지, 와일드카드와 PECS를 언제 쓰는지 이해하고, 실무에서 쓰는 제네릭 Repository / ApiResponse / Result 타입을 직접 설계할 수 있게 됩니다.
1. 왜 배우는가
실무 코드에서 List<Order>, Map<String, List<Payment>>, Optional<User>, CompletableFuture<Response> 를 하루에도 수십 번 봅니다. 제네릭을 "꺾쇠 괄호 안에 타입 적는 것" 정도로만 이해하면 List<? extends Number> 가 컴파일 에러를 내는 순간 막힙니다.
스프링의 ResponseEntity<T>, JPA의 JpaRepository<T, ID>, Jackson의 TypeReference<T> 는 전부 제네릭 위에 세워져 있어서, 제네릭을 모르면 프레임워크 시그니처를 읽을 수 없습니다.
제네릭의 본질은 "타입을 파라미터로 받는 코드" 입니다. 값을 파라미터로 받는 메서드가 재사용성을 만들듯이, 타입을 파라미터로 받는 클래스는 "어떤 타입이든 담되, 담은 타입 그대로 꺼내준다"는 계약을 컴파일러가 검증하게 만듭니다. 런타임 ClassCastException 을 컴파일 에러로 앞당기는 것 — 이것이 제네릭이 주는 가장 큰 가치입니다.
또한 제네릭은 API 설계 능력과 직결됩니다. ApiResponse<T> 하나를 잘 설계하면 컨트롤러 수백 개가 같은 포맷을 공유하고, Result<T, E> 를 도입하면 예외 대신 값으로 실패를 표현할 수 있습니다. 이 레슨은 문법이 아니라 왜 그렇게 동작하는지(타입 소거, 불공변) 에 집중합니다.