제네릭 타입 (Generic Types) – 자바에서 타입 안정성을 확보하다
728x90

1.1 제네릭의 개념 및 필요성

제네릭이란 무엇인가?

제네릭(Generic)은 클래스나 메서드를 다양한 타입으로 재사용할 수 있도록 도와주는 기능이다.

쉽게 말해, 데이터를 담을 때 "어떤 타입이 들어올지 미리 지정하지 않고, 사용자가 필요에 따라 지정하게 하는" 방식을 말한다.

Java 5부터 Generic이라는 타입이 새로 추가되었다.

 

제네릭은 왜 필요할까?

// 타입을 지정하지 않는 경우
List list = new ArrayList();
list.add("Hello");
list.add(123); 
// 다양한 타입이 들어갈 수 있음.

// 제네릭을 사용하는 경우
List<String> list = new ArrayList<>();
list.add("Hello");
// list.add(123);
// 타입 불일치로 컴파일 오류가 발생한다.

자바 5 이전에는 컬렉션 같은 클래스에 다양한 타입의 객체를 넣을 수 있었지만, 이러한 방식은 타입 오류가 런타임에 발생하는 문제가 있었다. 제네릭은 컴파일 시점에 타입 오류를 잡아내기 위해 도입되었다.

  1. 타입 안전성: 컴파일러가 타입을 검증하므로 런타임 오류가 줄어든다.
  2. 코드 재사용성: 다양한 타입에 대해 하나의 클래스나 메서드를 재사용할 수 있다.

1.2 제네릭 타입의 사용법

  • 제네릭 클래스와 메서드의 정의 및 예시
class Box<T> {
    private T content;
    
    public T getContent() { return content; }
    public void setContent(T content) { this.content = content; }
}


Box<String> stringBox = new Box<>();
stringBox.setContent("Hello");
System.out.println(stringBox.getContent());
  • 제네릭 타입의 경계(bound) 설정 방법 (<T extends Class>)
class NumberBox<T extends Number> {
    private T number;
    
    public NumberBox(T number) { this.number = number; }
    public double getDoubleValue() { return number.doubleValue(); }
}


NumberBox<Integer> intBox = new NumberBox<>(123);
System.out.println(intBox.getDoubleValue());

T extends Number는 제네릭 타입 T가 Number 타입이나 그 하위 클래스만 허용하도록 제한한다.

1.3 제네릭 관련 면접 질문

  • 제네릭 타입 사용 시 컴파일러의 역할은 무엇인가요?

자바 컴파일러는 제네릭 타입을 타입 소거(type erasure)를 통해 처리한다. 즉, 제네릭 타입은 컴파일 후 타입 정보를 제거하여, 실제로는 Object 타입으로 변환된다. 이렇게 변환되는 이유는 역호환성을 보장하고 제네릭 타입의 이점을 유지하기 위함이다.

역호환성을 유지한다는 것은 제네릭을 사용하지 않는 이전의 자바 코드가 여전히 작동할 수 있도록 한다는 것이다.

그래서 제네릭을 사용할 때는 컴파일 시점에 타입이 안전하게 검사되지만, 런타임에는 이 타입 정보가 없어져서 컴파일러가 자동으로 캐스팅을 추가해준다.


  • 제네릭 타입의 제한사항에 대해서 말해보세요.

배열 생성 불가: 제네릭 배열을 직접 생성할 수 없다.

// 오류 발생
List<Integer>[] intLists = new ArrayList<Integer>[10];

기본 타입 사용 불가: int, double 같은 기본 타입을 사용할 수 없고, 대신 Integer, Double 같은 래퍼 클래스를 사용해야 한다.

타입 검사: 런타임에 타입 검사를 할 수 없으므로, instanceof 연산자 사용에 제한이 있다.


1.4 예제 코드

class SumCalculator<T extends Number> {
    private T num1;
    private T num2;
    
    public SumCalculator(T num1, T num2) {
        this.num1 = num1;
        this.num2 = num2;
    }
    
    public double calculateSum() {
        return num1.doubleValue() + num2.doubleValue();
    }
}


SumCalculator<Integer> intSum = new SumCalculator<>(10, 20);
System.out.println("Integer Sum: " + intSum.calculateSum());

SumCalculator<Double> doubleSum = new SumCalculator<>(10.5, 20.3);
System.out.println("Double Sum: " + doubleSum.calculateSum());
  • T extends Number를 사용하여 숫자 타입만 허용
  • calculateSum 메서드는 doubleValue() 메서드를 사용해 제네릭 타입의 숫자 값을 더할 수 있게 함.

 

728x90
반응형