# ITEM26 로타입 raw type 은 사용하지 말라

  • 로타입을 사용하면 런타임 예외가 일어날 수 있으니 사용하면 안된다.
  • 제네릭 도입 이전 코드와의 호환성을 위해 제공될 뿐임

# 제네릭 타입 generic type

# 제네릭 클래스, 제네릭 인터페이스

클래스와 인터페이스 선언에 타입 매개변수가 쓰임

# List 인터페이스

원소타입을 나타내는 타입 매개변수 E 를 받는다.

  • List 인터페이스의 완전한 이름은 List<E> 이다.
  • 짧게 List 라고 자주 쓴다.
public interface List<E> extends Collection<E> {
    // ..
}
1
2
3

# 매개변수화 타입

제네릭 타입은 매개변수화 타입을 정의함

매개변수화 타입

  • 클래스(인터페이스)이름 <실제 타입 매개변수>

List<String>

  • 원소의 타입이 String 인 리스트를 뜻하는 매개변수화 타입
  • String
    • 정규 (formal) 타입 매개변수 E 에 해당
    • 실제 (actual) 타입 매개변수

# 로타입 raw type

  • 제네릭 타입을 정의 → 딸린 로타입도 함께 정의

# 의미

  • 제네릭 타입에서 타입 매개변수를 전혀 사용하지 않을 때

List<E> 의 로타입 → List

타입 선언에서 제네릭타입 정보가 전부 지워진 것처럼 동작함

private final Collection stamps = ...;
stamps.add(new Coin(...));  // "unchecked call" 경고를 내뱉는다.
1
2
  • Stamp 대신 Coin 을 넣어도 아무런 오류없이 컴파일 되고 실행된다.
  • 컴파일러가 모호한 경고메세지를 보여주긴 한다.

# 반복자의 로 타입 -> 런타임에 오류 발견 문제

for (Iterator i = stamps.iterator(); i.hasNext(); ) {
    Stamp stamp = (Stamp) i.next(); // ClassCastException 을 던진다.
    stamp.cancel();
}
1
2
3
4

# 매개변수화된 컬렉션 타입 - 타입 안전성 확보

private final Collection<Stamp> stamps = ...;
1

오류는 컴파일타임에 발견하는 것이 가장 이상적이다.

  • 컴파일러가 stamps 에는 Stamp 의 인스턴스만 넣어야 함을 인지
  • 컴파일러는 컬렉션에서 원소를 꺼내는 모든 곳 → 보이지 않는 형변환을 추가
    • 항상 성공

# 타협점

로타입은 절대 쓰면 안되지만, 언어차원에서는 호환성 때문에 지원하고 있다.

위험성

  • 제네릭이 안겨주는 안전성과 표현력을 모두 잃음

호환성

  • 자바가 제네릭을 받아들이기까지 10년이 걸린 탓에 제네릭 없이 짠 코드가 세상을 뒤덮어 버림

# List<Object>

# 로타입 List

  • 사용하면 안된다.
  • 제네릭 타입에서 완전히 발을 뺀 것
  • List<String> 을 넘길 수 있다

# List<Object>

임의 객체를 허용하는 매개변수화 타입

  • 사용해도 괜찮다
  • 모든 타입을 허용한다는 의미를 컴파일러에 명확히 전달한 것
  • List<String> 을 넘길 수 없다.

# 제네릭의 하위 타입 규칙

List<String>

  • 로타입 List 의 하위 타입 O
  • List<Object> 의 하위타입 X → 타입 안전

# 런타임에 실패한다 - unsafeAdd 메서드가 로타입 List 를 사용

컴파일은 되지만, 로타입 List 를 사용하여 경고발생,
하지만 런타임에서 ClassCastException 이 발생하고야 만다.

public static void main(String[] args) {
    List<String> strings = new ArrayList<>();
    unsafeAdd(strings, Integer.valueOf(42));
    String s = strings.get(0);  // ClassCastException, Integer → String
}

private statis void unsafeAdd(List list, Object o) {
    list.add(o);
}
1
2
3
4
5
6
7
8
9

List 를 매개변수화타입 List<Object> 로 바꾸면 컴파일 조차 되지 않게 된다.

# 비한정적 와일드카드 타입 unbounded wildcard type

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

  • 어떤 타입이라도 담을 수 있는 가장 범용적인 매개변수화 타입
  • Set<E>Set<?>
  • 타입안전, 유연함

잘못된 예 - 모르는 타입의 원소도 받는 로 타입을 사용함

static int numElementsInCommon(Set s1, Set s2) {
    int result = 0;
    for (Object o1 : s1) 
        if (s2.contains(o1)) 
            result++;
    return result;
}
1
2
3
4
5
6
7

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

static int numElementsInCommon(Set<?> s1, Set<?> s2) { ... }
1

# 비한정적 와일드카드 타입과 로타입의 차이점

  • 와일드카드타입 → 안전, 로타입 → 비안전

Collection<?>

  • null 외에는 어떤 원소도 넣을 수 없다. → else 컴파일 에러
  • 컬렉션에서 꺼낼 수 있는 객체의 타입도 전혀 알 수 없다.

# 로타입 규칙의 예외

# class 리터럴에는 로타입을 써야 한다.

자바 명세, class 리터럴

  • 매개변수화 타입을 사용하지 못한다.
    • List<String>.class, List<?>.class
  • 배열과 기본 타입만 허용한다.
    • List.class, String[].class, int.class

# instanceof 연산자

런타임에는 제네릭 타입 정보가 지워짐

  • instanceof 연산자
    • 매개변수화 타입 중, 비한정적 와일드카드 타입만 사용 가능
    • 로타입, 비한정적 와일드카드 모두 똑같이 동작함
      • 로타입사용 → 코드 깔끔
if (o instanceof Set) {
    Set<?> s = (Set<?>) o;
}
1
2
3

# 로타입, ...<Object>, ...<?>

...<Object>

  • 어떤 타입의 객체도 저장할 수 있는 매개변수화 타입

...<?>

  • 모종의 타입 객체만 저장할 수 있는 와일드카드 타입 ??

로타입

  • 제네릭 타입 시스템에 속하지 않음

...<Object>, ...<?>

  • 타입 안전

로타입

  • 타입 불안전