Programming Language/Java

[Java] 27. 제네릭 - Generic(1)

lumana 2025. 1. 13. 21:52

 

제네릭 - Generic(1)

#Java


정리

제네릭이 필요한 이유

코드 재사용을 늘리기 위해 Object와 다형성을 사용하면 타입 안전성이 떨어지는 문제가 발생한다.


  • 각각의 타입별로 IntegerBox, StringBox와 같은 클래스를 모두 정의
    • 코드 재사용X
    • 타입 안전성O
  • ObjectBox를 사용해서 다형성으로 하나의 클래스만 정의
    • 코드 재사용O
    • 타입 안전성X

제네릭 적용

제네릭을 사용하면 코드 재사용과 타입 안전성이라는 두 마리 토끼를 한 번에 잡을 수 있다.


예시

package generic.ex1;

public class GenericBox<T> {
    private T value;

    public void set(T value) {
        this.value = value;
    }

    public T get() {
        return value;
    }
}

  • <>를 사용한 클래스를 제네릭 클래스라 한다.
    • 이 기호(<>)를 보통 다이아몬드
  • 제네릭 클래스를 사용할 때는 Integer, String 같은 타입을 미리 결정하지 않는다.
  • 클래스명 오른쪽에 <T>와 같이 선언하면 제네릭 클래스가 된다.
  • T를 타입 매개변수라고 한다.
  • 클래스 내부에 T 타입이 필요한 곳에 T value와 같이 타입 매개변수를 적어두면 된다.

생성 시점에 원하는 타입 지정

제네릭 클래스는 생성하는 시점에 <> 사이에 원하는 타입을 지정한다.


new GenericBox<Integer>()

이렇게 하면 앞서 정의한 GenericBoxT가 다음과 같이 지정한 타입으로 변한 다음 생성된다.


원하는 모든 타입 사용 가능


제네릭 클래스를 사용하면 GenericBox 객체를 생성하는 시점에 원하는 타입을 마음껏 지정할 수 있다.


제네릭 컴파일 과정
제네릭을 도입한다고 해서 앞서 설명한 GenericBox<String>, GenericBox<Integer>와 같은 코드가 실제 만들어지는 것은 아니다. 대신에 자바 컴파일러가 우리가 입력한 타입 정보를 기반으로 이런 코드가 있다고 가정하고 컴파일 과정에 타입 정보를 반영한다. 이 과정에서 타입이 맞지 않으면 컴파일 오류가 발생한다.


타입 추론

GenericBox<Integer> integerBox2 = new GenericBox<>() // 타입 추론
자바는 왼쪽에 있는 변수를 선언할 때의 <Integer>를 보고 오른쪽에 있는 객체를 생성할 때 필요한 타입 정보를 얻을 수 있다. 따라서 new GenericBox<>()와 같이 타입 정보를 생략할 수 있다.
이렇게 자바가 스스로 타입 정보를 추론해서 개발자가 타입 정보를 생략할 수 있는 것을 타입 추론이라 한다.


참고로 타입 추론이 그냥 되는 것은 아니고, 자바 컴파일러가 타입을 추론할 수 있는 상황에만 가능하다.


제네릭 용어와 컨벤션

제네릭의 핵심은 사용할 타입을 미리 결정하지 않는다는 점이다. 생성 시점에 타입을 결정한다.


제네릭의 타입 매개변수와 타입 인자
정리하면 다음과 같다.

  • 메서드는 매개변수에 인자를 전달해서 사용할 값을 결정한다.
  • 제네릭 클래스는 타입 매개변수에 타입 인자를 전달해서 사용할 타입을 결정한다.

메서드에서 사용하는 용어를 그대로 가져다 사용하지만, 값이 아니라 타입을 결정하는 것이기 때문에 앞에 타입을 붙인다.


  • 타입 매개변수: GenericBox<T>에서 T
  • 타입 인자:
    • GenericBox<Integer>에서 Integer
    • GenericBox<String>에서 String

제네릭 타입의 타입 매개변수 <T>에 타입 인자를 전달해서 제네릭의 사용 타입을 결정한다.


  • 제네릭 타입 (Generic Type)
    • 클래스나 인터페이스를 정의할 때 타입 매개변수를 사용하는 것을 말한다.
    • 제네릭 클래스, 제네릭 인터페이스를 모두 합쳐서 제네릭 타입이라 한다.
      • 타입은 클래스, 인터페이스, 기본형(int 등)을 모두 합쳐서 부르는 말이다.
    • 예: class GenericBox<T> { private T t;}에서 GenericBox<T>
  • 타입 매개변수 (Type Parameter)
    • 제네릭 타입이나 메서드에서 사용되는 변수로, 실제 타입으로 대체된다.
    • 예:GenericBox<T> 에서의 T
  • 타입 인자 (Type Argument)
    • 제네릭 타입을 사용할 때 제공되는 실제 타입이다.

제네릭 명명 컨벤션

타입 매개변수는 일반적인 변수명처럼 소문자로 사용해도 문제는 없다. 하지만 일반적으로 대문자를 사용하고 용도에 맞는 단어의 첫 글자를 사용하는 관례를 따른다. 주로 사용하는 키워드는 다음과 같다.


  • E - Element
  • K - Key
  • N - Number
  • T - Type
  • V - Value
  • S, U, V etc. - 2nd, 3rd, 4th types

제네릭 타입에서는 여러 타입 매개변수를 선언할 수 있다. 만약 타입이 두 개면 T1, T2… 요런 식으로 붙인다.


타입 인자로 기본형은 사용할 수 없다
제네릭의 타입 인자로 기본형(int, double 등)은 사용할 수 없다. 대신 래퍼 클래스를 사용하면 된다.


로 타입

GenericBox integerBox = new GenericBox();
제네릭 타입을 사용할 때는 항상 <>를 사용해서 사용 시점에 원하는 타입을 지정해야 한다. 그런데 위과 같이 <>을 지정하지 않을 수 있는데, 이런 것을 로 타입(raw type), 또는 원시 타입이라 한다.


원시 타입을 사용하면 내부의 타입 매개변수가 Object로 사용된다고 이해하면 된다. 레거시 때문에 로 타입을 지원하는 것이다. (== 로 타입은 사용하지 말자). Object 타입을 쓸 거면 타입 인자로 Object를 직접 지정해주자.


예시


public class Box<T> {
    private T value;

    public void setValue(T value) {
        this.value = value;
    }

    public T getValue() {
        return value;
    }
}

  • Box 제네릭 클래스에 각각의 타입에 맞는 동물을 보관하고 꺼낸다.
    • Box<Dog> dogBox: Dog 타입을 보관할 수 있다.
    • Box<Cat> catBox: Cat 타입을 보관할 수 있다.
    • Box<Animal> animalBox: Animal 타입을 보관할 수 있다.
  • Dog과 Cat이 Animal의 하위 타입이므로, Box<Animal> 에 Dog과 Cat을 set하고 get할 수 있다.

참고: 김영한의 실전 자바 - 중급 2편