Topic (오늘의 주제)
자바 제네릭 (Java Generics)
: 클래스나 메서드 내부에서 사용할 데이터의 타입을 외부에서 지정하는 기법. "타입을 파라미터화(parameterize)"하여 컴파일 시점에 타입을 체크하는 기능이다.
Why (왜 사용하는가? 왜 중요한가?)
실무: 런타임에 발생할 수 있는 치명적인 ClassCastException을 컴파일 타임에 미리 잡아내어 시스템 안정성을 획기적으로 높인다.
구조적 의미: 타입 변환(Casting)을 제거하여 코드 가독성을 높이고, 하나의 코드로 여러 타입을 대응하는 **재사용성(Reusability)**을 제공한다.
면접 의도: 컴파일 타임과 런타임의 차이를 이해하는지, 그리고 제네릭의 동작 원리(Type Erasure)와 와일드카드 활용 능력을 확인하려 한다.
Core Concept (핵심 개념 정리)
| 요소 | 내용 |
| 개념 정의 | 데이터 타입을 미리 지정하지 않고, **<T>**와 같은 파라미터를 통해 나중에 확정하는 기법. |
| 동작 방식 | 1. 선언: class Box<T> { T item; } 2. 사용: Box<String>으로 선언 시 컴파일러가 T를 String으로 간주하고 체크. 3. 소거(Erasure): 컴파일 후 바이트코드(.class)에는 제네릭 정보가 지워지고 Object나 상위 타입으로 변환됨(하위 호환성). |
| 장점 | 1. 타입 안정성: 잘못된 타입이 들어오는 것을 컴파일러가 차단. 2. 캐스팅 제거: (String) list.get(0) 같은 강제 형변환 불필요. 3. 코드 재사용: 다양한 타입을 처리하는 공통 로직 작성 가능. |
| 필요 조건 | 제네릭 타입 파라미터에는 int, double 같은 원시 타입(Primitive Type)을 사용할 수 없다. 반드시 Integer, Double 같은 Wrapper 클래스를 써야 한다. |
| 비교 (vs) | Object 사용: 예전(Java 1.4 이하)에는 Object로 받아 매번 캐스팅해야 했으며, 런타임 에러 위험이 매우 컸음. |
Interview Answer Version (면접 답변식 요약)
"네, 실무에서 제네릭을 적극적으로 사용합니다. 제네릭을 사용하는 가장 큰 이유는 컴파일 시점의 타입 안정성(Type Safety) 확보입니다.
Object로 타입을 다룰 때 발생할 수 있는 런타임의 ClassCastException을 방지하고, 불필요한 강제 형변환(Casting) 코드를 줄여 가독성을 높일 수 있습니다. 또한, 공통된 로직을 여러 타입에 유연하게 적용할 수 있어 코드의 재사용성을 높이는 데 필수적이라고 생각합니다."
Practical Tip (사용시 주의할 점 or 활용 예)
1. 타입 소거 (Type Erasure) 주의
제네릭은 컴파일 타임에만 존재하고 런타임에는 사라진다(Erased).
- 문제: 런타임에 타입 정보를 알아야 하는 작업은 불가능하다.
- if (obj instanceof T) ❌ (불가능)
- new T() ❌ (불가능)
- new T[10] ❌ (제네릭 배열 생성 불가)
2. PECS 원칙 (와일드카드 활용)
제네릭 메서드를 설계할 때 유연성을 위해 ? (와일드카드)를 사용한다면 PECS 공식을 기억해야 한다.
- Producer Extends: 데이터를 생산(조회)만 한다면 <? extends T> (상한 경계)
- Consumer Super: 데이터를 소비(저장)만 한다면 <? super T> (하한 경계)
3. 실무 코드 예시 (ApiResponse 클래스)
API 응답을 래핑하는 공통 클래스에서 자주 사용된다.
Java
// 제네릭을 사용하여 어떤 데이터(User, Order, Product 등)든 담을 수 있는 응답 객체
@Getter
@AllArgsConstructor
public class ApiResponse<T> {
private String status;
private T data; // 상황에 따라 UserDto, OrderDto 등이 들어옴
private String message;
public static <T> ApiResponse<T> success(T data) {
return new ApiResponse<>("SUCCESS", data, null);
}
}
예상 꼬리질문 정리
- Q: 제네릭의 'Type Erasure(소거)'란 무엇이며 왜 존재하나요?
- A: 컴파일러가 제네릭 타입을 체크한 후, 런타임 시에는 해당 정보를 지우고 Object나 경계 타입으로 변경하는 것입니다. 이는 제네릭이 없던 Java 5 이전 버전의 코드와 하위 호환성을 유지하기 위함입니다.
- Q: List<String>은 List<Object>를 상속받나요? (공변성 질문)
- A: 아니요, 상속받지 않습니다. 제네릭은 기본적으로 **무공변(Invariant)**입니다. String은 Object의 자식이지만, List<String>은 List<Object>의 자식이 아닙니다. 이를 해결하려면 와일드카드(? extends Object)를 써야 합니다.
- Q: 제네릭 타입으로 배열을 생성할 수 없는 이유는 무엇인가요?
- A: 배열은 공변적(Covariant)이고 런타임에 타입을 실체화(Reified)해서 체크하지만, 제네릭은 불공변이고 런타임에 타입이 소거되기 때문입니다. 둘의 타입 체크 시점과 방식이 충돌하여 타입 안전성을 보장할 수 없기 때문에 금지되어 있습니다.
'Archive > Daily Dev Q&A' 카테고리의 다른 글
| Daily Dev Q&A: 프레임워크(Framework) (0) | 2025.12.13 |
|---|---|
| Daily Dev Q&A: 자바의 빌더 패턴(Builder Pattern) (0) | 2025.12.11 |
| Daily Dev Q&A: 자바의 접근 제어자(Access Modifier) (0) | 2025.12.09 |
| Daily Dev Q&A: 자바의 Collections Framework (0) | 2025.12.08 |
| Deily Dev Q&A: SOLID원칙 (0) | 2025.12.08 |