-
아이템26. 로 타입은 사용하지 말라책/이펙티브자바 2021. 8. 24. 11:52
제네릭
클래스와 인터페이스 선언에 타입 매개변수가 쓰이면, 이를 제네릭 클래스 혹은 제네릭 인터페이스라 한다.
제네릭 클래스와 제네릭 인터페이스를 통틀어
제네릭 타입(generic type)
이라 한다.각각의 제네릭 타입은 일련의
매개변수화 타입(parameterized type)
을 정의한다.예를들어, List은 원소의 타입이 String인 리스트를 뜻하는 매개변수화 타입이다.
List, 여기서 String 이 정규(formal) 타입 매개변수 E 에 해당하는 실제(actual) 타입 매개변수이다.
로 타입의 단점
제네릭 타입을 하나 정의하면 그에 딸린
로 타입(raw type)
도 함께 정의된다.로 타입이란 제네릭 타입에서 타입 매개변수를 전혀 사용하지 않을 때를 말한다.
예를들어, List 의 로 타입은 List 이다.
로 타입은 타입 선언에서 제네릭 타입 정보가 전부 지워진 것처럼 동작하는데, 제네릭이 도래하기 전 코드와 호환되도록 하기 위한 궁여지책이라 할 수 있다.
// Collection 의 로 타입 private final Collection stampes = ...; // 실수로 Coin을 넣는다. stamps.add(new Coin()); // "unchecked call"
이런식으로 로 타입으로 선언하게 되면 Stamp 대신 Coin 을 넣어도 아무 오류 없이 컴파일되고 실행된다.
// 반복자의 로 타입 for (Iterator i = stamps.iterator(); i.hasNext(); ) { Stamp stamp = (Stamp) i.next(); // ClassCastException stamp.cancel(); }
EJ 책에서 계속 오류는 가능한 한 발생 즉시, 이상적으로는 컴파일할 때 발견하는 것이 좋다 라고 한다.
위의 예시에서는 오류가 발생하고 한참 뒤인 런타임에야 알아챌 수 있는데, 이렇게 되면 런타임에 문제를 겪는 코드와 원인을 제공한 코드가 물리적으로 떨어져있을 가능성이 크다.
ClassCastException이 발생하면 찾기 위해 stamps 에 동전을 넣는 부분을 전부 봐야할 수도 있다.
제네릭의 장점
제네릭을 활용하면 위와 같은 문제를 해결할 수 있다.
// 매개변수화된 컬렉션 타입 - 타입 안전성 확보 private final Collection<Stamp> stamps = ...;
이렇게 선언하면 컴파일러는 Stamp 인스턴스만 넣어야함을 인지하게 된다.
이제 stamps에 엉뚱한 타입의 인스턴스를 넣으려 하면 컴파일 오류가 발생하며 무엇이 잘못인지 알려준다.
error: incompatiable types: Coin cannot be converted stamps.add(new Coin());
제네릭을 활용하면 컴파일러는 컬렉션에서 원소를 꺼내는 모든 곳에 보이지 않는 형변환을 추가하여 절대 실패하지 않음을 보장한다.
쓰면 안되는 로타입을 만들어 놓은 이유
앞에서도 언급하듯, 로 타입을 쓰는 걸 언어 차원에서 막아 놓지는 않았지만 절대로 써서는 안 된다.
로 타입을 쓰면 제네릭이 안겨주는 안전성과 표현력을 모두 잃게 된다.
그러면 왜 써서는 안되는 로 타입을 애초에 만들어 놓은것일까 ?
그것은 자바가 제네릭을 받아들이기 전에 제네릭 없이 짠 코드가 이미 많이 쓰옇기에 호환성 때문이다.
List<Object>
List 같은 로 타입은 사용해서는 안 되나, List<Object> 처럼 임의 객체를 허용하는 매개변수화 타입은 괜찮다.
List 는 제네릭 타입에서 완전히 발을 뺀 것이고, List<Object> 는 모든 타입을 허용한다는 의사를 컴파일러에 명확히 전달한 것이다.
매개변수로 List 를 받는 메서드에 List<String> 은 넘길 수 있지만, List<Object>
이는 제네릭의 하위 타입 규칙 때문인데, List<String>은 로 타입인 List의 하위 타입이지만, List<Object>의 하위타입은 아닌 것이다.
그 결과, List<Object>같은 매개변수화 타입을 사용할 때와 달리 List 같은 로 타입을 사용하면 타입 안전성을 잃게된다.
// 런타임에 실패 - unsafeAdd()가 로타입 사용 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); }
이 코드로 컴파일은 되지만 strings.get(0); 을 할때 ClassCastException 을 던진다.
Integer → String 으로 변환 시도했기 때문이다.
이러한 형변환은 보통 IDE에서 잡아줘서 실패하지 않지만 이 경우엔 컴파일러의 경고를 무시해서 그런것이다.
// 컴파일에 실패 private static void unsafeAdd(List<Object> list, Object o) { list.add(o); }
위의 unsafeAdd 메서드에서 로타입 List를 List<Object>
비한정적 와일드카드 타입
// 모르는 타입의 원소도 받는 로타입을 사용한 잘못된 예 static int numElementsInCommon(Set s1, Set s2) { int result = 0; for (Object o : s1) if (s2.contains(o1)) result++; return result; }
이 메서드는 동작은 하지만 로 타입을 사용해 안전하지 않다.
따라서
비한정적 와일드 카드 타입 (unbounded wildcard type)
을 대신 사용하는게 좋다.제네릭 타입을 쓰고 싶지만 실제 타입 매개변수가 무엇인지 신경 쓰고 싶지 않다면 물음표 (?) 를 사용하자.
예를들어, 제네릭 타입인 Set의 비한정적 와일드 카드 타입은 Set<?> 이다.
이것이 어떤 타입이라도 담을 수 있는 가장 범용적인 매개변수화 Set 타입이다.
// 비한정적 와일드카드 타입 사용 static int numElementsInCommon(Set<?> s1, Set<?> s2) { ... }
그러면 Set<?> 와 로타입 Set 의 차이는 무엇일까 ?
간단히 말하자면 와일드카드 타입은 안전하고, 로 타입은 안전하지 않다.
로 타입 컬렉션에는 아무 원소나 넣을 수 있으니 타입 불변식을 훼손하기 쉽다.
반면, Collection<?> 에는 (null 외에는) 어떤 원소도 넣을 수 없다.
구체적으로는, null 외의 어떤 원소도 Collection<?>에 넣지 못하게 했으며 컬렉션에서 꺼낼 수있는 객체의 타입도 전혀 알 수 없게 했다.
로타입을 쓰는 경우
로 타입을 쓰지 말라는 규칙에도 소소한 예외가 몇 개 있다.
class 리터럴에는 로 타입을 써야 한다.
자바 명세에는 class 리터럴에 매개변수화 타입을 사용하지 못하게 했다. (배열과 기본 타입은 허용한다.)
예를들어, List.class, String[].class, int.class 는 허용하고 List.class, List<?>.class는 허용하지 않는다.
instanceof 연산자와 관련이 있다.
런타임에는 제네릭 타입 정보가 지워지므로 instanceof 연산자는 비한정적 와일드 카드 타입 이외의 매개변수화 타입에는 적용할 수 없다.
그리고 로타입이든 비한정적와일드카드 타입이든 instanceof에는 똑같이 동작한다.
// 로타입을 써도 좋은 예 - instanceof if (o instanceof Set) { Set<?> s = (Set<?>)o; ... }
용어정리
결론
- 로 타입을 사용하면 런타임에 예외가 일어날 수 있으니 사용하면 안 된다.
- Set
- Set
'책 > 이펙티브자바' 카테고리의 다른 글
아이템34. int 상수 대신 열거 타입을 사용하라 (0) 2021.08.28 아이템27. 비검사 경고를 제거하라 (0) 2021.08.24 아이템25. 톱레벨 클래스는 한 파일에 하나만 담으라 (0) 2021.08.24 아이템23. 태그 달린 클래스보다는 클래스 계층구조를 활용하라 (0) 2021.08.24 아이템22. 인터페이스는 타입을 정의하는 용도로만 사용하라 (0) 2021.08.24