💻 프로그래밍

제네릭

Generic

타입을 파라미터화하여 재사용 가능한 코드를 작성하는 기법. Java, TypeScript, Rust 등에서 지원.

📖 상세 설명

제네릭(Generic)은 타입을 파라미터화하여 재사용 가능한 컴포넌트를 작성하는 프로그래밍 기법입니다. 함수나 클래스를 특정 타입에 종속시키지 않고, 사용 시점에 타입을 지정할 수 있게 합니다. 이를 통해 타입 안정성을 유지하면서도 코드 중복을 줄일 수 있습니다.

제네릭의 핵심 이점은 컴파일 타임 타입 안정성입니다. Java의 List<String>은 문자열만 저장할 수 있고, 잘못된 타입 추가는 컴파일 오류로 잡힙니다. Object를 사용하면 런타임에 ClassCastException이 발생할 수 있지만, 제네릭은 이를 컴파일 타임에 방지합니다.

TypeScript에서 제네릭은 함수, 클래스, 인터페이스에 적용됩니다. Array<T>, Promise<T>, Map<K, V> 등 많은 내장 타입이 제네릭입니다. 제약 조건(extends)을 사용해 타입 파라미터를 특정 인터페이스를 구현한 타입으로 제한할 수도 있습니다.

Rust에서는 제네릭이 성능 저하 없이 동작합니다. 컴파일러가 단형화(monomorphization)를 통해 사용되는 각 타입에 대해 별도의 코드를 생성합니다. 이는 런타임 오버헤드 없이 제네릭의 편의성을 제공합니다. 트레이트 바운드로 타입 제약을 표현합니다.

💻 코드 예제

// TypeScript 제네릭 예제

// 제네릭 함수
function identity<T>(arg: T): T {
    return arg;
}

const str = identity<string>("hello");  // 타입 명시
const num = identity(42);                // 타입 추론

// 제네릭 인터페이스
interface Result<T, E> {
    success: boolean;
    data?: T;
    error?: E;
}

// 제네릭 클래스
class Container<T> {
    private value: T;

    constructor(value: T) {
        this.value = value;
    }

    getValue(): T {
        return this.value;
    }
}

// 제약 조건 (extends)
interface HasLength {
    length: number;
}

function logLength<T extends HasLength>(arg: T): number {
    console.log(arg.length);
    return arg.length;
}

logLength("hello");     // OK - string has length
logLength([1, 2, 3]);   // OK - array has length
// logLength(123);      // Error - number doesn't have length

// 여러 타입 파라미터
function swap<T, U>(tuple: [T, U]): [U, T] {
    return [tuple[1], tuple[0]];
}

const swapped = swap([1, "hello"]);  // [string, number]

🗣️ 실무 대화 예시

프론트엔드 개발자: "API 호출 함수가 엔드포인트마다 거의 똑같은데, 응답 타입만 다릅니다. 코드 중복이 심해요."

기술 리더: "제네릭 함수로 추상화하면 됩니다. fetch<T>(url): Promise<T> 형태로 만들어 보세요."

프론트엔드 개발자: "호출 시 타입을 지정하면 반환값도 그 타입으로 추론되겠네요."

기술 리더: "네, Axios 인스턴스도 그렇게 구현되어 있어요. 에러 타입도 제네릭으로 받으면 더 완벽합니다."

면접관: "제네릭을 사용하는 이유와 장점을 설명해 주세요."

지원자: "타입 안정성을 유지하면서 재사용 가능한 코드를 작성할 수 있습니다. any를 쓰면 타입 검사가 무력화되지만, 제네릭은 사용 시점에 타입이 결정되어 IDE 자동완성과 컴파일 오류 검출이 가능합니다."

면접관: "공변성(covariance)과 반공변성(contravariance)에 대해 아시나요?"

지원자: "공변성은 T가 U의 하위 타입이면 Container<T>도 Container<U>의 하위 타입인 경우입니다. 반공변성은 그 반대이고, 함수 파라미터 위치에서 발생합니다. TypeScript에서는 이를 엄격하게 검사합니다."

리뷰어: "이 유틸리티 함수가 any를 반환하고 있어서 타입 안정성이 없네요."

작성자: "여러 타입에서 사용되어서 any로 했는데, 더 좋은 방법이 있을까요?"

리뷰어: "제네릭을 사용하세요. 입력 타입에서 반환 타입을 추론하게 하면 any 없이도 유연성을 유지할 수 있습니다."

작성자: "알겠습니다. T extends 조건도 추가해서 잘못된 타입 전달을 막겠습니다."

⚠️ 주의사항

🔗 관련 용어

📚 더 배우기