제네릭

제네릭 (Generics)

같은 로직을 여러 타입에 대해 재사용하고 싶다면 어떻게 해야 할까요? 타입을 파라미터로 넘겨서 해결하는 게 제네릭(Generics) 이에요. 클래스·인터페이스·메서드를 정의할 때 타입을 하나의 인자처럼 두고, 사용하는 시점에 구체 타입을 결정할 수 있죠. dev.java의 제네릭 시리즈는 타입 파라미터의 개념부터 형식 추론, 와일드카드, 타입 이레이저, 제약사항까지 한 흐름으로 짚어 줘요.

출처: Generics — Dev.java

타입 파라미터 소개

제네릭을 쓰면 메서드의 형식 매개변수(parameter)처럼 타입도 매개변수로 다룰 수 있어요. 정의할 때 쓴 타입 파라미터(예: T) 자리에, 실제 사용할 때 구체 타입(예: String)을 넣어 같은 코드를 여러 입력에 재사용합니다.

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");

형식 추론 (Type Inference)

형식 추론은 자바 컴파일러가 메서드 호출과 그 선언을 비교해 타입 인자를 알아내는 능력이에요. 덕분에 우리는 일일이 타입을 적지 않아도 되고, 자바 7 이후의 다이아몬드 연산자 <>로 생성자 호출에서 타입 생략이 가능해졌죠. 추론 덕에 제네릭 코드가 훨씬 간결해진답니다.

와일드카드 (Wildcards)

제네릭 코드에서 물음표(?)는 와일드카드라고 해서 '알 수 없는 타입'을 나타내요. 상한 경계 와일드카드(? extends T), 하한 경계 와일드카드(? super T), 와일드카드 캡처처럼 쓰임새에 따라 세부 규칙이 나뉘어요. 이 규칙을 이해하면 '어떤 타입이든 받는' 유연하면서도 타입 안전한 코드를 설계할 수 있어요.

타입 이레이저 (Type Erasure)

타입 이레이저는 파라미터화된 타입 때문에 새 클래스를 만들지 않도록 보장해요. 컴파일 타임에 타입 파라미터를 실제 타입(또는 경계 타입)으로 치환하고, 필요한 곳에 캐스트를 넣는 방식으로 동작하죠. 그 결과 제네릭은 런타임 오버헤드를 일으키지 않는데, 동시에 런타임에는 타입 정보가 사라진다는 의미이기도 해요. 이 원리를 알면 제네릭과 관련된 제약(예: 원시 타입 못 쓰기, 배열 생성 제한 등)이 왜 생기는지도 자연스럽게 이해됩니다.

더 알아보기