Quick Syntax
List<String> names = new ArrayList<>();
names.add("Kim");
String first = names.get(0);제네릭은 넣는 타입과 꺼내는 타입을 컴파일 시점에 연결해 cast와 ClassCastException을 줄입니다. runtime에는 type erasure가 적용되므로, generic type argument 자체를 검사해야 하는 경계에서는 별도 type token이나 값 검증이 필요합니다.
문법
어떤 제네릭 형태를 먼저 떠올리면 되나
| 상황 | 먼저 떠올릴 것 |
|---|---|
| 타입 안전한 컬렉션 | List<String> 같은 제네릭 타입 |
| 다양한 타입에 재사용 | 제네릭 클래스 |
| 반환 타입이 입력 타입과 연결됨 | 제네릭 메서드 |
| raw type 경고 제거 | 타입 파라미터 명시 |
제네릭이 없었을 때의 문제
제네릭이 없던 시절에는 컬렉션에서 값을 꺼낼 때 항상 Object로 받고 cast해야 했습니다. 잘못된 타입이 들어가도 런타임에서야 ClassCastException이 발생했습니다. 제네릭은 이런 오류를 컴파일 타임으로 끌어올립니다.
// ❌ raw type 사용 (제네릭 없음) — 런타임 오류 위험
List list = new ArrayList();
list.add("hello");
list.add(42); // 다른 타입도 들어감
String s = (String) list.get(1); // ClassCastException!
// ✅ 제네릭 사용 — 컴파일 타임에 오류 감지
List<String> list = new ArrayList<>();
list.add("hello");
list.add(42); // 컴파일 오류 — String이 아님타입 매개변수와 재사용
제네릭의 핵심은 코드 재사용보다 타입 관계 보존입니다. 같은 자료구조라도 어떤 타입을 담는지 정확히 표현할 수 있어 API가 안전하고 읽기 쉬워집니다.
// 제네릭 클래스: 어떤 타입의 쌍이든 담을 수 있음
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; }
}
Pair<String, Integer> entry = new Pair<>("Alice", 42);
String name = entry.getFirst(); // cast 없이 String
Integer score = entry.getSecond(); // cast 없이 Integer
// 제네릭 메서드: 반환 타입이 입력 타입과 연결됨
public static <T> T firstOrNull(List<T> list) {
return list.isEmpty() ? null : list.get(0);
}
String first = firstOrNull(names); // cast 없이 String으로 반환
Integer num = firstOrNull(numbers);raw type을 피해야 하는 이유
raw type(List list)을 쓰면 컴파일러 경고가 나고 제네릭의 이점이 사라집니다. Java 제네릭은 타입 소거(type erasure) 방식으로 구현돼 runtime에 원소 type argument까지 검사할 수 없습니다. container type만 확인할 때는 raw type 대신 instanceof List<?>를 쓰고, 외부 데이터라면 원소를 개별 검증합니다.
if (value instanceof List<?> items) {
boolean allStrings = items.stream().allMatch(String.class::isInstance);
}체크포인트
| 상황 | 적합한 선택 |
|---|---|
| 타입 안전한 컬렉션 | List<String>, Map<String, Integer> |
| 다양한 타입에 재사용 가능한 유틸 | 제네릭 메서드 <T> |
| cast 없이 값 꺼내기 | 제네릭 컬렉션 사용 |
| raw type 경고 | 반드시 타입 파라미터 명시 |
| 다음 단계 | wildcard (?), bounded type (<T extends Number>) |
주의할 점
raw type은 제네릭의 모든 이점을 없앱니다. 타입 파라미터를 항상 명시하세요.
// ❌ raw type — 컴파일 경고 + 런타임 ClassCastException 위험
List list = new ArrayList();
list.add("hello");
list.add(123);
String s = (String) list.get(1); // 런타임 오류!
// ✅ 제네릭 명시 — 컴파일 타임에 오류 잡음
List<String> list = new ArrayList<>();
list.add("hello");
// list.add(123); // 컴파일 오류 — String이 아님
String s = list.get(0); // cast 불필요참고 링크
2 sources