포스트

JAVA (16) - 제네릭

JAVA (16) - 제네릭

1. 개요

자바에서 제네릭(Generic)은 클래스, 인터페이스, 메서드를 정의할 때 사용할 데이터 타입을 파라미터로 지정할 수 있게 해주는 기능이다. Java 5에 도입되었으며, 데이터 타입(Data Type)의 안정성을 높이고 형변환의 번거로움을 줄이기 위해 사용된다. 다양한 타입의 객체들을 다루는 컬렉션 프레임워크 등에서 필수적으로 활용된다.

2. 제네릭의 필요성과 장점

제네릭이 도입되기 전에는 여러 타입을 다루기 위해 최상위 클래스인 Object 타입을 사용했다. 하지만 Object 타입을 사용하면 객체를 꺼낼 때마다 명시적 형변환(Casting)이 필요했고, 잘못된 타입이 들어갈 경우 런타임 에러가 발생할 위험이 컸다.

제네릭을 사용하면 다음과 같은 장점을 얻는다.

  • 컴파일 타임 타입 검사: 런타임에 발생할 수 있는 ClassCastException을 컴파일 시점에 미리 찾아내어 방지한다.
  • 불필요한 형변환 제거: 객체를 꺼내올 때 명시적으로 타입 변환을 하지 않아도 되므로 코드가 간결해진다.
  • 코드 재사용성 증가: 하나의 클래스나 메서드로 다양한 데이터 타입을 처리할 수 있다.
1
2
3
4
5
6
7
8
9
10
// 제네릭 사용 전
List list = new ArrayList();
list.add("hello");
String str = (String) list.get(0); // 명시적 형변환 필요

// 제네릭 사용 후
List<String> list = new ArrayList<>();
list.add("hello");
String str = list.get(0); // 형변환 생략 가능, 타입 안정성 확보

3. 제네릭의 기본 문법 및 관례

제네릭 타입은 클래스나 메서드 이름 옆에 <T>와 같은 형태로 타입을 지정하여 선언한다.

타입 파라미터 관례

개발자 간의 약속으로 통용되는 대표적인 타입 파라미터 기호는 다음과 같다.

  • <T> : Type (타입)
  • <E> : Element (요소, 주로 컬렉션에서 사용)
  • <K> : Key (키)
  • <V> : Value (값)
  • <N> : Number (숫자)
1
2
3
4
5
6
7
8
9
10
11
12
13
// 제네릭 클래스 정의
public class Box<T> {
    private T item;

    public void setItem(T item) {
        this.item = item;
    }

    public T getItem() {
        return item;
    }
}

4. 제네릭 메서드와 제한된 타입 파라미터

(1) 제네릭 메서드

메서드의 리턴 타입 앞에 제네릭 타입을 선언하여 사용하는 메서드다. 클래스가 제네릭이 아니더라도 메서드 단위로 제네릭을 적용할 수 있다.

1
2
3
4
5
public <T> T printAndReturn(T item) {
    System.out.println(item);
    return item;
}

(2) 제한된 타입 파라미터 (Bounded Type Parameter)

extends 키워드를 사용하면 특정 클래스의 하위 타입이나 특정 인터페이스를 구현한 타입으로만 제네릭을 제한할 수 있다.

1
2
3
4
5
// Number 클래스와 그 하위 클래스(Integer, Double 등)만 허용
public class NumberBox<T extends Number> {
    private T number;
}

5. 와일드카드 (?)

제네릭 타입에서 지정할 타입을 정확히 알지 못하거나 범위를 다르게 지정해야 할 때 와일드카드 ?를 사용한다.

  • <?> (Unbounded Wildcard): 모든 타입을 허용한다. (<? extends Object>와 동일)
  • <? extends T> (Upper Bounded Wildcard): T와 그 하위 타입만 허용한다. (상한 제한, 읽기 전용으로 자주 사용)
  • <? super T> (Lower Bounded Wildcard): T와 그 상위 타입만 허용한다. (하한 제한, 쓰기 전용으로 자주 사용)

6. 제네릭 타입 소거 (Type Erasure)

자바의 제네릭은 하위 호환성을 유지하기 위해 컴파일 시점에만 타입을 검사하고 런타임에는 제네릭 타입 정보를 제거(Erasure)하는 방식으로 동작한다.

  • 컴파일러는 제네릭 타입을 바이트 코드로 변환할 때 상한 타입(기본적으로는 Object, extends가 있으면 제한된 타입)으로 변환한다.
  • 이 때문에 런타임에는 제네릭 타입 정보가 존재하지 않아 new T()와 같은 인스턴스 직접 생성이나 제네릭 배열 생성(new T[10])이 불가능하다.

7. 정리

제네릭은 자바에서 객체의 타입을 컴파일 시점에 지정하여 타입 안정성을 확보하고 불필요한 형변환을 줄이는 핵심 기능이다. 와일드카드와 제한된 타입 파라미터를 사용하면 유연한 다형성 설계를 구현할 수 있다.

단, 자바 내부적으로 컴파일 시 제네릭 타입이 제거되는 타입 소거(Type Erasure)가 일어난다는 특성을 숙지하고 사용하는 것이 필요하다.

이 기사는 저작권자의 CC BY 4.0 라이센스를 따릅니다.