[Effective Java] 로 타입은 사용하지 말라

로 타입

로 타입은 자바5 이전의 코드와 호환성을 위해 유지하는 것이지 사용해서는 안된다. 다음은 로 타입 사용으로 인해 ClassCastException을 Runtime에 던지는 예이다.

1
2
3
4
5
6
7
8
9
10
public static void main(String[] args) {
List<String> strings = new ArrayList<>();

unsafeAdd(strings, Integer.valueOf(42));
String s = strings.get(0);
}

private static void unsafeAdd(List list, Object o) {
list.add(o);
}

이런 오류를 겪는 코드는 문제를 겪는 코드와 원인을 제공한 코드가 물리적으로 상당히 떨어져 있을 가능성이 크다. 제네릭 타입을 사용하면 제네릭 타입은 일련의 매개변수화 타입을 정의하고 컴파일러에게 해당 타입의 인스턴스만 삽입할 것을 알려줄 수 있다. 따라서 아무런 경고 없이 동작한다면 의도대로 동작할 것임을 보증하는 것이다.

만약 모든 타입을 받을 수 있는 컬렉션을 원한다면 List\처럼 임의 객체를 허용하는 매개변수화 타입을 선언하자. 그럼 List와 List\의 차이는 무엇일까? List는 제네릭 타입에서 완전히 발을 뺀 것이고 List\는 모든 타입을 허용한다는 의미를 컴파일러에게 명확히 전달한 것이다. 매개변수로 List를 받는 메소드에 List\은 전달할 수 있지만 List\를 받는 메소드에는 불가능하다. 이는 제네릭의 하위 타입 규칙 때문이다. List\은 List의 하위 타입이지만 List\의 하위 타입은 아니다. 그 결과 List같은 로 타입을 사용하면 타입 안전성을 잃게 된다.

만약 제네릭 타입을 쓰고 싶지만 타입 매개변수가 무엇인지 신경쓰고 싶지 않을 때는 ?를 쓰자. 제네릭 타입인 Set\의 비한정적 와일드 카드 타입은 Set\<?>다. 그럼 Set과 Set\<?>의 차이는 무엇일까? 로 타입 컬렉션에는 아무 원소를 넣을 수 있지만 Collection\<?>에는 null 외에는 어떤 원소도 넣을 수 없다. 다른 원소를 넣을 시엔 컴파일 타임에 오류를 내보인다.

로 타입을 사용하는 예

class 리터럴

클래스 리터럴에는 로 타입을 사용해야 한다. 자바 명세에서는 클래스 리터럴에 매개변수화 타입의 사용을 못하게 했다. 즉, List.class, String[].class, int.class는 허용하나 List\.class와 같은 방식은 허용하지 않는다.

instanceof

런타임에는 제네릭 타입 정보가 지워지므로 instanceof 연산자는 비한정적 와일드 카드 타입 이외의 매개변수화 타입에는 적용할 수 없다. 그리고 로 타입이든 비한정적 매개변수 타입이든 instanceof는 동일하게 동작한다. 비한정적 와일드카드 타입의 꺾쇠괄호와 물음표는 아무런 역할 없이 코드만 지저분하게 하니 차라리 로 타입을 쓰는 편이 깔끔하다.

1
2
3
if( o instanceof Set) {
Set<?> s = (Set<?>) o;
}
Author: Song Hayoung
Link: https://songhayoung.github.io/2020/08/10/Languages/Effective%20JAVA/item26/
Copyright Notice: All articles in this blog are licensed under CC BY-NC-SA 4.0 unless stating additionally.