자바 제너릭 (Generics) 정리
1. 자바와 컴파일의 특징
자바는 흔히 컴파일 언어라고 하지만, 실제로는 하이브리드 언어다.
- .java → javac → .class(바이트코드)로 컴파일
- JVM이 이 .class를 읽어들여 인터프리터처럼 실행하다가, JIT 컴파일러로 최적화된 기계어를 만들어 실행
즉, 컴파일 + 인터프리터 두 방식을 혼합한다.
왜 굳이 컴파일을 할까?
- 문제 조기 발견
실행하기 전에 타입이나 문법 문제를 확인할 수 있다.
예: Integer b = a; (여기서 a가 Object라면) → 컴파일 에러 발생. - 최적화 가능
전체 코드를 보고 중복 제거, 인라이닝 같은 최적화를 할 수 있다.
정적 타입 언어답게, 자바는 상위타입 → 하위타입 암시적 대입을 금지한다.
Object obj = Integer.valueOf(1); // 업캐스팅 OK
Integer num = obj; // 다운캐스팅은 불가 (컴파일 에러)
명시적 캐스팅 없이는 불가하다. 이런 엄격함이 자유도를 줄이는 대신, 버그를 조기에 방지해준다.
2. 제너릭이 없던 시절
예전엔 모든 컬렉션이 raw type 이었다.
List list = new ArrayList();
list.add(10);
list.add("oops"); // 컴파일 에러 없음
Integer n = (Integer) list.get(1); // 실행 중 ClassCastException
즉,
- 런타임에 오류가 터진다 (발견 시점이 늦음)
- 읽을 때마다 캐스팅 필요 (귀찮음 + 가독성↓)
이 문제 때문에 자바 5부터 제너릭(Generics) 이 도입됐다. 이제는 컴파일 단계에서 잘못된 타입 삽입을 막을 수 있다.
List<Integer> list = new ArrayList<>();
list.add(10);
// list.add("oops"); // 컴파일 에러
3. 제너릭 클래스 기초
class Box<T> {
private T item;
public Box(T item) { this.item = item; }
public T getItem() { return item; }
}
- T : 타입 매개변수(Type Parameter)
- Box<Toy> : 타입 인자(Type Argument) 를 넘겨 실제 타입을 지정한 것
- <T> : 타입 매개변수 섹션
타입을 Object로 두는 것과 제너릭은 다르다.
- Object로 하면 캐스팅 필요 + 오류가 런타임에 발견됨
- 제너릭은 컴파일 타임에 타입 체크로 안전성을 확보
4. 제너릭과 상한 경계 (Upper Bound)
때로는 “이 타입은 무조건 ○○을 구현해야 한다”는 제약을 걸고 싶을 때가 있다. 이때 상한 경계(bound) 를 쓴다.
interface Boxable {
default boolean isBreakable() { return false; }
}
class Toy implements Boxable { }
class Box<T extends Boxable> { // T는 Boxable 하위 타입만 가능
private T item;
public Box(T item) { this.item = item; }
public T getItem() { return item; }
}
- Box<T> → 아무 타입이나 가능
- Box<T extends Boxable> → Boxable을 구현한 타입만 가능
즉, 컴파일 시점에 계약을 명시하는 셈이다.
여기에 여러 개의 제약도 걸 수 있다. (단, 클래스는 1개만, 인터페이스는 여러 개 가능)
5. 제너릭 메서드
클래스 자체가 제너릭일 수도 있지만, 메서드만 제너릭일 수도 있다.
class Box<T extends Boxable> {
private T item;
private String dest;
public Box(T item, String dest) {
this.item = item;
this.dest = dest;
}
// 제너릭 메서드 (U는 Boxable 하위 타입만 가능)
public <U extends Boxable> boolean hasSameDestination(Box<U> other) {
return this.dest.equals(other.dest);
}
}
- 클래스의 T와 메서드의 U는 별개 타입 변수다.
- 즉, Box<Toy>와 Box<Glass>도 주소 비교가 가능하다.
6. 제너릭 생성자와 static 메서드
- 생성자에도 제너릭 선언이 가능하다.
public <U extends Number> Box(T item, String dest, U price) {
this.item = item;
this.dest = dest;
System.out.println("가격: " + price.intValue());
}
→ Integer, Double 등 어떤 Number든 받을 수 있다.
- static 메서드는 클래스의 T를 쓸 수 없다. 필요하다면 메서드 자체에서 타입을 선언해야 한다.
public static <U extends Boxable> List<U> collect(List<Box<U>> boxes) {
List<U> result = new ArrayList<>();
for (Box<U> b : boxes) result.add(b.getItem());
return result;
}
7. 원시 타입(raw type) 사용 자제
Box rawBox = new Box(new Toy()); // 가능은 하지만 경고 발생
- 타입 안전성이 사라지고, 매번 캐스팅해야 한다.
- 레거시 코드 호환 외에는 피하는 게 좋다.
'자바 > 자바' 카테고리의 다른 글
| (자바) 인프런 강의(자바 ) *널널한 개발자 (0) | 2025.09.02 |
|---|---|
| 인프런 강의 정리_ JAVA 성능 튜닝과 트러블 슈팅 * 휴먼넷 (1) | 2025.08.31 |
| (자바)정렬 Comparator vs Comparable 완전 정리 (1) | 2025.08.07 |
| (자바) equals(), hashCode(), toString()의 역할 (0) | 2025.07.07 |
| (JAVA) java.lang 패키지란 (0) | 2025.07.06 |