Java 제네릭과 타입 소거 (Generic & Type Erasure)
요약
Java 제네릭(Generic)의 개념과 타입 소거(Type Erasure)를 알아봅니다. Collection<Integer>로 선언해도 런타임에는 Object로 처리되어 오버로딩 메서드 선택에 영향을 주는 원리를 이해하여 정보처리기사 실기 문제를 풀어봅니다.
제네릭과 타입 소거 핵심 정리
| 개념 | 설명 | 예시 |
|---|---|---|
| 제네릭 | 타입을 파라미터로 받는 클래스/메서드 | Collection<Integer> |
| 타입 소거 | 컴파일 후 T가 구체적인 타입(Object 또는 제한 타입)으로 대체됨 | <T> → Object, <T extends Number> → Number |
| 오버로딩 결정 시점 | 컴파일 타임에 매개변수 타입으로 결정 | <T>이면 Object 기준, <T extends Number>이면 Number 기준으로 메서드 선택 |
| 핵심 결론 | 제네릭 타입은 런타임에 소거된 타입으로 취급 | extends 유무에 따라 호출 메서드가 달라짐 (아래 심화 섹션에서 설명) |
제네릭이란? 쌩기초
제네릭(Generic) 은 클래스나 메서드를 정의할 때 타입을 미리 정하지 않고, 사용할 때 지정하는 기능입니다.
쉽게 말하면, "어떤 타입이든 담을 수 있는 상자" 를 만드는 것입니다.
여기서 T는 타입 파라미터라고 부릅니다. 실제로 사용할 때 구체적인 타입을 지정합니다.
| 코드 | T의 타입 | value의 타입 |
|---|---|---|
Box<Integer> | Integer | Integer |
Box<String> | String | String |
Box<Double> | Double | Double |
잠깐, 컴파일과 런타임이 뭔가요? 기초
타입 소거를 이해하려면 컴파일과 런타임의 차이를 먼저 알아야 합니다. 자세한 설명은 컴파일과 런타임의 차이를 참고하세요.
간단히 정리하면:
- 컴파일:
.java파일을.class파일로 변환하는 과정 - 런타임: 변환된
.class파일이 실제로 실행되는 시점
타입 소거는 컴파일 과정에서 일어납니다. 아래에서 자세히 알아보겠습니다.
타입 소거란? 기초
타입 소거(Type Erasure) 는 Java의 제네릭이 컴파일 후에 타입 정보가 제거되는 것을 말합니다.
핵심: 컴파일 전 vs 컴파일 후
컴파일러가 제네릭 타입 T를 모두 Object로 바꿔버립니다. 이것이 타입 소거입니다.
| 시점 | T의 타입 | value의 타입 |
|---|---|---|
| 컴파일 전 (소스 코드) | Integer (지정한 타입) | Integer |
| 컴파일 후 (실행 코드) | 제거됨 | Object |
코드에서 뭘 보면 되나요?
시험 문제에서 타입 소거를 적용해야 하는지 확인하는 방법입니다.
<T>확인: 클래스나 메서드에<T>같은 타입 파라미터가 있으면 → 타입 소거 대상extends확인:<T extends Number>처럼 제한이 있으면 → Number로 소거.<T>만 있으면 → Object로 소거. 이 차이가 오버로딩 결과를 바꿉니다 (아래 심화 섹션에서 구체적인 예시를 확인하세요)- 오버로딩 확인: 같은 이름의 메서드가 여러 개면 → 소거된 타입 기준으로 매칭
타입에 제한이 있으면? (bounded type erasure, 제한된 타입 소거) 심화
위에서 <T>는 Object로 소거된다고 배웠습니다. 그런데 <T extends Number>처럼 타입에 제한이 있으면 결과가 달라집니다.
사실 <T>는 <T extends Object>입니다
타입 소거 규칙은 하나입니다: T는 항상 extends 뒤의 타입으로 소거됩니다.
<T>라고만 쓰면 Java는 이것을 <T extends Object>로 취급합니다. 그래서 T가 Object로 소거되는 것입니다. <T extends Number>라고 쓰면 T는 Number로 소거됩니다. 규칙은 같고, extends 뒤에 뭘 적느냐만 다릅니다.
| 선언 | 실제 의미 | 소거 후 T의 타입 |
|---|---|---|
<T> | <T extends Object> | Object |
<T extends Number> | <T extends Number> | Number |
<T extends Comparable> | <T extends Comparable> | Comparable1 |
코드로 비교
왜 중요한가?
오버로딩과 만나면 결과가 완전히 달라집니다. 아래는 원리를 단순하게 보여주기 위한 별도 예제입니다 (위에서 사용한 Collection + Printer 예제와 구조는 동일합니다).
이 오버로딩 메서드에 제네릭 클래스의 data를 전달한다고 합시다.
| 제네릭 선언 | 소거 후 data 타입 | 호출되는 메서드 | 반환값 |
|---|---|---|---|
<T> | Object | check(Object o) | "O" |
<T extends Number> | Number | check(Number n) | "N" |
같은 코드인데 extends Number 유무에 따라 "O"와 "N"으로 결과가 갈립니다.
타입 소거와 오버로딩 기초
이 개념이 시험에 출제되는 핵심입니다. 오버로딩과 타입 소거가 만나면 예상과 다른 결과가 나올 수 있습니다. 오버로드 선택이 컴파일 시점에 어떤 기준으로 결정되는지는 Java 컴파일타임 바인딩 페이지에서 자세히 다룹니다.
문제 상황
Printer 클래스에는 같은 이름의 print 메서드가 3개 있습니다. 매개변수 타입만 다릅니다.
오버로딩에서 배운 대로, Java는 전달하는 인자의 타입에 따라 어떤 메서드를 호출할지 결정합니다.
| 호출 코드 | 인자 타입 | 호출되는 메서드 |
|---|---|---|
print(new Integer(5)) | Integer | print(Integer x) -> "A5" |
print(new Object()) | Object | print(Object x) -> "B..." |
print(3.14) | Double (Number의 하위) | print(Number x) -> "C3.14" |
제네릭 클래스에서 호출하면?
이 클래스를 다음과 같이 사용한다고 합시다.
Collection<Integer>이니까 value는 Integer입니다. 그러면 new Printer().print(value)에서 위에 있는 Printer의 print(Integer x)가 호출될 것 같습니다.
하지만 실제로는 Printer의 print(Object x) 가 호출됩니다.
왜 print(Object x) 가 호출될까?
오버로딩의 메서드 결정은 컴파일 타임에 이루어집니다.
타입 소거로 T가 Object로 바뀌기 때문에, 컴파일러는 print(Object x) 메서드를 선택합니다.
자주 하는 실수 심화
| 실수 | 올바른 이해 |
|---|---|
Collection<Integer>이니까 print(Integer x) 호출 | 타입 소거 후 T는 Object이므로 print(Object x) 호출 |
| 런타임에 value가 Integer이니까 Integer 메서드 호출 | 오버로딩은 컴파일 타임에 결정, 런타임 타입과 무관 |
| Number는 Integer의 부모이니까 print(Number x) 호출 | 컴파일 타임 타입이 Object이므로 Object만 매칭 |
문제 풀이 전략 심화
풀이 순서
- 제네릭 클래스 확인:
<T>같은 타입 파라미터가 있는지 확인 extends확인:<T extends X>이면 T → X로 소거 (예:<T extends Number>이면 T →Number),<T>이면 T → Object로 소거- 오버로딩 메서드 확인: 소거된 타입으로 어떤 메서드가 호출되는지 판단
- 결과 도출: 선택된 메서드의 출력 확인
핵심 공식
이 흐름만 기억하면 제네릭 + 오버로딩 문제를 풀 수 있습니다.
정보처리기사 실기 기출 문제
Footnotes
-
Comparable은 Java에서 비교 가능한 타입임을 나타내는 인터페이스입니다.compareTo()메서드를 제공하며,Integer,String등이 이를 구현합니다. ↩