자바 제네릭 클래스(Java Generic Class) 개념

Java Generic(제네릭)이 등장한 이유와 핵심 개념
제네릭(Generic)이란?
제네릭은 클래스나 메서드가 사용할 데이터 타입을 미리 고정하지 않고, 사용하는 시점에 타입을 지정할 수 있도록 하는 기능이다.
쉽게 말해,
"타입을 변수처럼 받아서 클래스나 메서드를 일반화하는 기능"
이라고 볼 수 있다.
대표적인 예로 ArrayList가 있다.
List<String> names = new ArrayList<>();
List<Integer> numbers = new ArrayList<>();
같은 ArrayList 클래스지만 사용하는 타입에 따라 전혀 다른 타입의 리스트처럼 동작한다.
제네릭은 왜 등장했을까?
제네릭의 가장 중요한 목적은
런타임 오류를 컴파일 타임 오류로 앞당기기 위해서
이다.
과거에는 다양한 타입을 저장하기 위해 Object를 사용했다.
List list = new ArrayList();
list.add("Hello");
list.add(100);
Object는 모든 클래스의 부모이므로 어떤 타입이든 저장할 수 있다.
문제는 꺼낼 때 발생한다.
String value = (String) list.get(1);
실제로는 Integer가 들어있는데 String으로 캐스팅하면 런타임에 예외가 발생한다.
ClassCastException
즉,
- 저장할 때는 문제 없음
- 컴파일도 성공
- 실행 중에 오류 발생
이라는 문제가 있었다.
Object의 한계
Object를 사용하는 방식은 타입 검증을 개발자가 직접 해야 한다.
Object obj = list.get(0);
if(obj instanceof String){
String str = (String)obj;
}
이처럼 매번 타입을 확인하고 캐스팅해야 한다.
즉,
- 실수하기 쉽다.
- 캐스팅 코드가 많아진다.
- 런타임 오류가 발생할 수 있다.
제네릭이 해결한 문제
제네릭을 사용하면 컴파일러가 타입을 추적할 수 있다.
List<String> list = new ArrayList<>();
list.add("Hello");
list.add("World");
이때 아래 코드는 컴파일 자체가 되지 않는다.
list.add(100);
오류가 실행 중이 아니라 개발 단계에서 발견된다.
따라서 제네릭의 핵심 가치는
타입 안정성(Type Safety)
이다.
캐스팅 제거는 부가적인 이점일 뿐이다.
String value = list.get(0);
더 이상 캐스팅이 필요하지 않다.
제네릭의 본질
제네릭은
타입 간의 관계를 컴파일러가 안전하게 관리하도록 만드는 시스템
이라고 볼 수 있다.
라이브러리 작성자는 특정 타입에 종속되지 않은 코드를 만들고,
사용자는 원하는 타입을 지정할 수 있다.
Box<String>
Box<Integer>
Box<User>
동일한 코드가 다양한 타입에 대해 안전하게 동작한다.
제네릭 클래스
클래스 선언 시 타입을 매개변수로 받을 수 있다.
public class Box<T> {
private T value;
public void set(T value){
this.value = value;
}
public T get(){
return value;
}
}
사용 예시
Box<String> box = new Box<>();
box.set("Hello");
String value = box.get();
컴파일러는 T를 String으로 간주하여 타입 검사를 수행한다.
제네릭 클래스 활용
1. 공통 응답 규격
: API 개발 시 상태 코드, 메시지 등의 공통 구조는 유지하면서 실제 전달할 데이터(data)의 타입만 매번 다를 때 사용한다.
public class ApiResponse<T> {
private final T data; //제네릭 필드
private final Integer status;
private final String msg;
}
2. 값이 있을 수도/없을 수도 - Optional

T는 특별한 키워드가 아니다
많은 사람들이 T를 자바 문법의 예약어처럼 생각하지만 그렇지 않다.
public class Box<MyType> {
}
이렇게 작성해도 된다.
다만 관례적으로 아래 이름들을 사용한다.
| 이름 | 의미 |
| T | Type |
| E | Element |
| K | Key |
| V | Value |
| N | Number |
| R | Return |
즉, T는 단순한 이름일 뿐이다.
제네릭 메서드
제네릭은 클래스뿐 아니라 메서드에도 사용할 수 있다.
public static <T> T identity(T value){
return value;
}
구조를 보면 다음과 같다.
public static <T> T identity(T value)
│ │ │
│ │ └─ 매개변수 타입
│ └─────────── 반환 타입
└─────────────── 타입 매개변수 선언
여기서
<T>
는 해당 메서드가 사용할 타입 매개변수를 선언하는 부분이다.
없으면 컴파일러는 T가 무엇인지 알 수 없다.
클래스 제네릭과 메서드 제네릭은 다르다
다음 코드를 보자.
public class Box<T> {
public <T> T print(T value){
return value;
}
}
위 코드의 두 T는 서로 다른 타입 매개변수다.
즉,
- 클래스의 T
- 메서드의 T
는 독립적으로 동작한다.
또한 제네릭 메서드는 제네릭 클래스 내부에만 존재하는 것도 아니다.
public class Util {
public static <T> T identity(T value){
return value;
}
}
일반 클래스에서도 얼마든지 사용할 수 있다.
다이아몬드 연산자(<>)와 와일드카드(?)
다이아몬드 연산자
List<String> list = new ArrayList<>();
<>는 왼쪽 타입을 보고 컴파일러가 타입을 추론한다.
위 코드는 사실상 다음과 같다.
List<String> list = new ArrayList<String>();
와일드카드
List<?> list
는
"어떤 타입인지는 모르지만 무언가의 리스트"
라는 의미이다.
List<String>
List<Integer>
List<User>
모두 받을 수 있다.
중요한 점은
와일드카드는 타입 추론이 아니라 타입 미지정 상태를 의미한다.
는 것이다.
다만 타입 정보를 알 수 없기 때문에 요소를 꺼낼 때는 가장 상위 타입인 Object로만 받을 수 있다.
Object value = list.get(0);
컴파일러는 리스트 내부에 어떤 타입이 들어있는지 보장할 수 없기 때문이다.
또한 와일드카드는 타입이 아직 정해지지 않은 상태를 의미하지 않는다.
List<String> names = new ArrayList<>();
List<?> list = names;
위 코드에서 실제 타입은 이미 String으로 정해져 있다.
다만 list를 사용하는 코드 입장에서는 그 타입을 알 수 없도록 표현한 것이다.
즉,
와일드카드는 "타입 미정"이 아니라 "타입 비공개"에 가깝다.
런타임에는 어떻게 동작할까?
흥미로운 점은 제네릭이 컴파일 단계에서만 존재한다는 것이다.
List<String>
List<Integer>
는 컴파일 이후 타입 정보가 제거된다.
이를 Type Erasure(타입 소거) 라고 한다.
실행 시점에는 대략 다음과 같이 동작한다.
List
또는
Object
기반 코드로 변환된다.
즉,
- 컴파일 시점 → 타입 검사 수행
- 런타임 시점 → 타입 정보 제거
라는 방식으로 동작한다.
정리
제네릭의 핵심은 단순히 T를 사용하는 문법이 아니다.
과거 Object 기반 프로그래밍의 한계를 해결하기 위해 등장했으며,
- 타입 안정성을 확보하고
- 런타임 오류를 컴파일 타임 오류로 전환하며
- 불필요한 캐스팅을 제거하고
- 타입 관계를 컴파일러가 관리할 수 있도록 만든다.
한 문장으로 요약하면,
제네릭은 "타입 간의 관계를 컴파일러가 안전하게 관리하기 위한 시스템"이다.
'Language > Java' 카테고리의 다른 글
| 자바 제네릭, 와일드카드(?)와 PECS 원칙 (0) | 2026.09.20 |
|---|---|
| 자바 제네릭, <T>만으로는 부족하다 - 타입 제한(extends) (1) | 2026.09.14 |
| Java Stream API (0) | 2026.09.07 |
| [기술서적] 객체지향의 사실과 오해 (0) | 2025.10.20 |
| [Java] 부동소수점 오차를 피하자! (0) | 2025.03.11 |
