Menu
Coddy logo textTech

제네릭 소개

Coddy Java 여정의 객체 지향 프로그래밍 섹션에 포함된 레슨 — 87개 중 53번째.

값을 저장하고 나중에 이를 검색하는 클래스를 만들어야 한다고 가정해 봅시다. 제네릭이 없다면 각 타입에 대해 별도의 클래스를 작성해야 합니다. Integer를 위한 클래스 하나, String을 위한 클래스 하나, Double을 위한 클래스 하나 등등 말이죠. 이는 코드 중복과 유지보수의 어려움으로 이어집니다.

제네릭은 타입 안정성을 유지하면서도 모든 타입에서 작동하는 클래스와 메서드를 작성할 수 있게 함으로써 이 문제를 해결합니다. 클래스를 사용할 때 실제 타입으로 대체되는 자리 표시자(타입 매개변수라고 함)를 정의합니다:

// 제네릭이 없는 경우 - Object를 사용하며, 형변환이 필요함
List list = new ArrayList();
list.add("Hello");
String s = (String) list.get(0);  // 수동 형변환 필요

// 제네릭을 사용하는 경우 - 타입이 안전하며, 형변환이 필요 없음
List<String> list = new ArrayList<String>();
list.add("Hello");
String s = list.get(0);  // 형변환 필요 없음

꺾쇠괄호 <T>는 타입 매개변수를 나타냅니다. List<String>이라고 작성하면, 이 리스트에는 문자열만 포함될 것임을 컴파일러에게 알리는 것입니다. 만약 정수를 추가하려고 하면, 런타임에 오류가 발생하는 대신 컴파일러가 즉시 오류를 잡아냅니다.

제네릭은 두 가지 주요 이점을 제공합니다. 명시적인 형 변환의 필요성을 없애주고, 런타임이 아닌 컴파일 타임에 타입 오류를 잡아냅니다. 이는 코드를 더 안전하고 깔끔하게 만들어 줍니다. 다음 레슨에서는 직접 제네릭 클래스와 메서드를 만드는 방법을 살펴보겠습니다.

challenge icon

챌린지

쉬움

이전 방식(캐스팅과 함께 Object 사용)과 새로운 방식(타입 안정성을 갖춘 제네릭 사용)을 비교하여 제네릭의 강력함을 보여주는 간단한 저장 시스템을 만들어 봅시다.

차이점을 직접 확인하기 위해 세 개의 파일을 생성하게 됩니다:

  • OldBox.java: 제네릭 이전 방식으로 값을 저장하는 클래스를 만듭니다. OldBox는 Object 타입의 private 필드 content를 가져야 합니다. Object를 받아 저장하는 생성자와 Object를 반환하는 getContent() 메서드를 포함하세요. 이 방식은 작동은 하지만 값을 가져올 때 캐스팅이 필요합니다.
  • GenericBox.java: 이제 제네릭을 사용하여 현대적이고 타입 안전한 버전을 만듭니다. GenericBox는 선언부에 타입 파라미터 T를 사용해야 합니다. T 타입의 private 필드 content, T 값을 받는 생성자, 그리고 T 타입을 반환하는 getContent() 메서드가 필요합니다. 이 클래스를 사용할 때는 캐스팅이 필요하지 않습니다!
  • Main.java: 두 가지 방식을 나란히 시연합니다. 단어(String)와 숫자(integer) 두 개의 입력을 받게 됩니다.

    먼저, 이전 방식을 사용합니다: 단어를 저장하는 OldBox를 생성한 다음, String으로 캐스팅하여 가져와서 출력합니다: OldBox (with cast): [value]

    다음으로, 제네릭 방식을 사용합니다: 동일한 단어를 저장하는 GenericBox<String>를 생성하고, 캐스팅 없이 가져와서 출력합니다: GenericBox (no cast): [value]

    그런 다음 정수와 함께 작동하는 제네릭을 보여줍니다: 숫자를 저장하는 GenericBox<Integer>를 생성하고, 값을 가져와서 출력합니다: GenericBox Integer: [value]

    마지막으로, 빈 줄을 하나 출력한 뒤 다음을 출력합니다: Type safety: Generics catch errors at compile time!

단어(String)와 숫자(문자열 형태의 정수, 파싱 필요) 두 개의 입력을 순서대로 받게 됩니다.

주요 차이점에 주목하세요: OldBox를 사용할 때는 (String) box.getContent()와 같이 캐스팅을 해야 하며 타입이 맞기를 바라야 합니다. 반면 GenericBox를 사용하면 컴파일러가 타입을 알고 있으므로 캐스팅이 필요하지 않습니다. 이것이 제네릭의 핵심 이점인 캐스팅의 번거로움 없는 타입 안정성입니다!

치트 시트

제네릭(Generics)을 사용하면 타입 안정성을 유지하면서 모든 타입에서 작동하는 클래스와 메서드를 작성할 수 있습니다. 제네릭은 클래스가 사용될 때 실제 타입으로 대체되는 타입 매개변수(자리 표시자)를 사용합니다.

제네릭을 사용하지 않는 경우 (Object 사용):

List list = new ArrayList();
list.add("Hello");
String s = (String) list.get(0);  // 수동 캐스팅이 필요함

제네릭을 사용하는 경우 (타입 안전):

List<String> list = new ArrayList<String>();
list.add("Hello");
String s = list.get(0);  // 캐스팅이 필요 없음

제네릭 클래스 생성하기:

public class GenericBox<T> {
    private T content;
    
    public GenericBox(T content) {
        this.content = content;
    }
    
    public T getContent() {
        return content;
    }
}

제네릭 클래스 사용하기:

GenericBox<String> stringBox = new GenericBox<String>("Hello");
String value = stringBox.getContent();  // 캐스팅 없음

GenericBox<Integer> intBox = new GenericBox<Integer>(42);
Integer number = intBox.getContent();  // 타입 안전

꺾쇠괄호 <T>는 타입 매개변수를 나타냅니다. 장점으로는 명시적인 캐스팅이 필요 없으며, 런타임이 아닌 컴파일 타임에 타입 오류를 잡을 수 있다는 점이 있습니다.

직접 해보기

import java.util.Scanner;

class Main {
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        String word = scanner.nextLine();
        int number = Integer.parseInt(scanner.nextLine());
        
        // TODO: word를 저장하는 OldBox 생성
        // String으로 캐스팅하여 검색하고 출력: "OldBox (with cast): [value]"
        
        // TODO: word를 저장하는 GenericBox<String> 생성
        // 캐스팅 없이 검색하고 출력: "GenericBox (no cast): [value]"
        
        // TODO: number를 저장하는 GenericBox<Integer> 생성
        // 검색하고 출력: "GenericBox Integer: [value]"
        
        // TODO: 빈 줄을 출력한 후, 다음을 출력:
        // "Type safety: Generics catch errors at compile time!"
    }
}
quiz icon실력 점검

이 레슨에는 짧은 퀴즈가 포함되어 있습니다. 레슨을 시작해 문제를 풀고 진행 상황을 기록하세요.

객체 지향 프로그래밍의 모든 레슨