Item26. 로 타입은 사용하지 말라!
용어 정리
- 제네릭 클래스(제네릭 인터페이스) 클래스와 인터페이스 선언에 타입 매개변수가 쓰이는 경우 제네릭 클래스 혹은 인터페이스라 칭한다.
제네릭 타입 제네릭 클래스와 제네릭 인터페이스를 통틀어 제네릭 타입이라 칭한다.
- 매개변수화 타입 제네릭 타입은 일련의 매개변수화 타입을 정의한다. 클래스(인터페이스) 이름이 나오고, 이어서 꺽쇠괄호 안에 실제 타입 매개변수를 나열한다.
- 로 타입 제네릭 타입을 하나 정의하면 그에 딸린 로 타입도 함께 정의된다. 제네릭 타입에서 타입 매개변수를 전혀 사용하지 않을 때를 말한다.
로 타입 (Raw Type)
로 타입이 뭔가?
- 제네릭 타입에서 타입 매개변수를 전혀 사용하지 않을 때를 말함.
- 예를 들어
List<E>
의 로 타입은List
다.
왜 사용하는가?
- 제네릭이 도래하기 전 코드와 호환되도록 하기 위한 궁여지책이다.
- 로 타입은 제네릭 타입 정보가 전부 지워진 것처럼 동작하니 호환 가능하긴 함.
예시
로 타입
1
2
3
4
5
// Stamp 인스턴스만 취급한다.
private final Collection stamps = ...;
// 실수로 Stamp가 아닌 동전을 넣는다.
stamps.add(new Coin(...)) // "unchecked call" 경고를 내뱉는다.
- 아무 문제 없이 컴파일 및 실행된다.
- 컬렉션에서 이 동전을 다시 꺼내기 전까지는 오류를 찾지 못한다.
제네릭 타입
1
2
private final Collection<Stamp> stamps = ...;
stamps.add(new Coin(...)) // 컴파일 에러
stamps
에는Stamp
의 인스턴스만 넣어야 함을 컴파일러가 인지하게 된다.- 이제 실수로 동전을 넣으려 시도한다면 컴파일 오류가 발생하며 잘못된 점을 알려준다.
로 타입을 사용하지 말아야 하는 이유
- 로 타입을 사용하면 제네릭이 안겨주는 안전성과 표현력을 모두 잃게 된다.
로 타입의 역할
- 호환성
- 자바가 제네릭을 받아들이기까지 걸린 시간 : 거의 10년
- 이미 제네릭 없이 짠 코드가 세상을 뒤덮어 버리게 되었다.
- 기존 코드를 모두 수용하면서 제네릭을 사용할 수 있어야 했다.
- 이는 로 타입을 사용하는 메서드에 매개변수화 타입 인스턴스를 넘겨도 동작해야만 했던 것이다.(반대 상황도 동일)
- 마이그레이션 호환성을 위해 로 타입을 지원하고, 제네릭 구현에 소거 방식을 사용.
- 자바가 제네릭을 받아들이기까지 걸린 시간 : 거의 10년
매개변수화 타입
List
같은 로 타입은 안되지만,List<Object>
처럼 임의 객체를 허용하는 매개변수화 타입은 사용해도 괜찮다.- 제네릭 타입에서 완전히 발을 뺀 로 타입과는 달리,
List<Obejct>
는 모든 타입을 허용한다는 의도를 컴파일러에 명확히 전달하고 있다. List<Object>
는List
의 하위 타입이 아니다.List<Object>
같은 매개변수화 타입 사용 때와는 달리List
같은 로 타입 사용 시, 타입 안전성을 잃게 된다.
와일드 카드 타입
- 제네릭 타입을 쓰고 싶지만 실제 타입 매개변수가 무엇인지 신경쓰고 싶지 않을 수 있다.
- 이런 경우,
물음표(?)
를 사용하자.?
를 통해 비한정적 와일드카드 타입을 만들 수 있다.
1
static int numElementsInCommon(Set<?> set1, Set<?> set2) {....}
- 이런 경우,
- 와일드 카드 타입은 안전하고, 로 타입은 안전하지 않다.
- 로 타입은 아무 원소나 대입 가능해 타입 불변식을 훼손하기 쉽다.
- 반면 와일드 카드 타입인
Collection<?>
에는null
외에는 어떤 원소도 넣을 수 없다.- 비한정적 와일드카드 타입이다. 애초에 실제 타입을 대입하려는 것이 아닌 로 타입 대체 역할이기에 제약이 있다.
- 비한정적 와일드카드 타입의 살제 타입을 고려하지 않는 제약에서 벗어나려면 한정적 와일드카드 타입을 사용하면 된다.
예외
class
리터럴에는 로 타입을 써야 한다.- 자바 명세는
class
리터럴에 매개변수화 타입을 사용하지 못하게 했다.
- 자바 명세는
instanceof
연산자- 런타임에는 제네릭 타입 정보가 지워지므로
instanceof
연산자는 비한정적 와일드카드 타입 이외의 매개변수화 타입에는 적용이 불가하다. - 즉, 이 경우 꺽쇠괄호 부분은 코드만 지저분해지게 하는 요소이니, 제거해 로 타입을 사용한다.
1 2 3
if (o instanceof Set) { // 로 타입 Set<?> s = (Set<?>) o; // 와일드카드 타입 }
- 런타임에는 제네릭 타입 정보가 지워지므로
핵심 정리
- 로 타입을 사용하면 런타임에 예외가 일어날 수 있으니 사용하면 안 된다.
- 로 타입은 제네릭이 도입되기 이전 코드화의 호환성을 위해 제공될 뿐이다.
Set<Object>
: 어떤 타입의 객체도 저장 가능한 매개변수화 타입 (안전)Set<?>
: 모종의 타입 객체만 저장할 수 있는 와일드카드 타입 (안전)Set
: 로 타입, 제네릭 타입 시스템에 속하지 않는다. (불안전)
Comments powered by Disqus.