Post

Item 26 - 로 타입은 사용하지 말라

클래스와 인터페이스 선언에 타입 매개변수가 쓰이면, 제네릭 클래스 혹은 제네릭 인터페이스라 한다.

또 이들을 통틀어 제네릭 타입이라고 한다.

로 타입(raw type)

  • 제네릭 타입을 하나 정의하면 그에 딸린 로 타입(raw type)도 함께 정의된다.
  • 제네릭 타입에서 타입 매개변수를 사용하지 않을 때를 로 타입이라고 한다.
    • ex. List<E>의 로 타입은 List다.

로 타입을 사용하면 안되는 이유

로 타입은 사용하지 않는 것이 좋다.

1
2
3
4
5
// Stamp 인스턴스만 취급한다.
private final Collection stamps = ...;

// 실수로 동전을 넣는다.
stamps.add(new Coin(...)); // Coin 인스턴스를 add() 할 경우, "unchecked call" 경고를 내뱉는다.

위 코드는 도장(Stamp) 대신 동전(Coin)을 넣어도 아무 오류 없이 컴파일되고 실행된다.

런타임에 가서야 오류 여부를 알 수 있다는 단점이 존재하는 코드다.

오류는 가능한 컴파일할 때 발견하는 것이 좋다.

따라서 아래와 같이 제네릭을 활용하여 타입 안정성을 확보하는 것이 좋다.

1
2
private final Collection<Stamp> stamps = ...; // Stamp 인스턴스만 취급한다.
stamps.add(new Coin(...)); // Coin 인스턴스를 add() 한다면 컴파일 오류 발생

자바가 로 타입을 지원하는 이유

  • 절대로 써서는 안되는 로 타입이 존재하는 이유는 바로 호환성 때문이다.
    • 제네릭은 자바 5부터 도입되었다. (거의 10년은 제네릭 없이 코드를 짠 셈이다.)
  • 기존 코드를 모두 수용하면서 제네릭을 사용하는 새로운 코드와도 맞물려 돌아가게 해야만 했다.
    • 즉, 마이그레이션 호환성을 위해 로타입을 지원하는 것이다.

매개변수화 타입 사용

List 같은 로 타입은 사용해서는 안 되나, List<Object>처럼 임의 객체를 허용하는 매개변수화 타입은 괜찮다.

  • List<Object>는 모든 타입을 허용한다는 의사를 컴파일러에 명확히 전달한 것이다.
    • 매개변수로 List를 받는 메서드에 List<String>을 넘길 수 있다. ⭕
    • 매개변수로 List<Object>를 받는 메서드에는 List<String>을 넘길 수 없다. ❌ (하위타입규칙)
    • List<Object>와 같은 매개변수화 타입을 사용할 때와 달리 List 같은 로 타입을 사용하면 타입 안정성을 잃게 된다. 🤦‍♀️

비한정적 와일드 카드 타입을 사용하라

제네릭 타입을 쓰고 싶지만 실제 타입 매개변수가 무엇인지 신경 쓰고 싶지 않다면,

로 타입을 사용하지 말고 비한정적 와일드 카드 타입을 사용하라. (물음표(?)로 타입을 작성하자.)

1
2
3
4
5
// raw type : 사용 금지
static int numElementsInCommon(Set set1, Set s1) { ... }

// 비한정적 와일드카드 타입 - 타입 안정하며 유연하다.
static int numElementsInCommon(Set<?> s1, Set<?> s2) { ... }
  • 어떤 타입이라도 담을 수 있는 범용적인 매개변수화 타입이다.
  • 타입 불변식을 훼손하지 못하게 막았기 때문에 다른 원소를 넣으려 하면 컴파일 오류가 발생한다.

로타입을 쓰는 예외 상황

  • class 리터럴에는 로타입을 써야한다.
    • 자바 명세는 class 리터럴에 매개변수화 타입을 사용하지 못하게 했다.
    • List<String>.classList<?>.class는 허용되지 않고, List.class만 가능하다.
  • instanceof 연산자를 사용할 경우 로 타입을 쓰는 편이 나을 수 있다.
    • 런타임에는 제네릭 타입 정보가 지워지므로, instanceof 연산자로 타입이나 비한정적 와일드카드 타입 이외의 매개변수화 타입에는 적용할 수 없다.
1
2
3
if (o instanceof Set) { // 로 타입
    Set<?> s = (Set<?>)o; // 와일드카드 타입
}

o의 타입이 Set임을 확인한 후에 와일드카드 타입인 Set<?>로 형변환해야 한다.

💡 핵심 정리

  • 로 타입은 결코 사용해선 안된다.
    • 이것을 컴파일러가 허용하도록 둔 이유는 자바 1.5 버전 이전의호환성 때문이다.
  • 제네릭은 특정 파라미터 타입만을 제한한다.
    • 따라서, 모든 파라미터 타입을 허용하도록 하고 싶다면 비한정적 와일드카드를 사용하자.
This post is licensed under CC BY 4.0 by the author.