이펙티브 자바, 쉽게 정리하기 - item 26. 로 타입은 사용하지 말라
용어 정리
- 클래스 혹은 인터페이스 선언에 타입 매개변수가 쓰이면 이를 제네릭 클래스(generic class) 혹은 제네릭 인터페이스(generic interface)라 한다.
- 이를 통틀어 제네릭 타입(generic type)이라고 한다.
- 각각의 제네릭 타입은 일련의 매개변수화 타입(parameterized type)을 정의한다.
- 제네릭 타입을 정의하면 그에 딸린 로 타입(raw type)도 함께 정의된다.
- 로 타입이란 제네릭 타입에서 타입 매개변수를 전혀 사용하지 않을 때를 말한다. ex)
List
,Set
- 로 타입은 타입 선언에서 제네릭 타입 정보가 전부 지워진 것처럼 동작한다.
- 제네릭이 도래하기 전 코드와 호환되도록 하기 위한 궁여지책이라고 할 수 있다.
- 로 타입이란 제네릭 타입에서 타입 매개변수를 전혀 사용하지 않을 때를 말한다. ex)
로타입이 문제가 될 때
// 제네릭을 지원하기 전엔 컬렉션을 아래와 같이 선언했다.
private final Collection stamps = ...;
stamps.add(new Coin(...));
for (Iterator it = stamps.iterator(); i.hasNext();) {
Stamp stamp = (Stamp) i.next(); // ClassCastException
stamp.cancel();
}
- 여기서 발생한
ClassCastException
의 예외를 로그에서 보더라도add(new Coin(...))
코드를 찾기 위해 한참동안 삽질을 할 수 있다. - 로 타입을 쓰면 제네릭이 안겨주는 안정성과 표현력을 모두 잃게 된다.
절대 써서는 안될 로타입이 있는 이유는 오직 하위버전과의 호환성 때문이다.
List<Object>
와 List
의 차이
List<Object>
는 사용해도 되고,List
는 사용하면 안 된다.List<Object>
는 제네릭에 명확히Object
의 하위 타입을 받겠다고 선언한 것이고,List
는 제네릭을 버린 것이다.
List
로타입의 경우
public class Item26Test {
@Test
public void rawTypeTest() {
List<String> strings = new ArrayList<>();
unsafeAdd(strings, Integer.valueOf(42));
String s = strings.get(0);
System.out.println("strings = " + s);
}
private void unsafeAdd(List list, Object o) {
list.add(o);
}
}
- 컴파일 전엔 아무런 에러가 발생하지 않는다.
- 컴파일 뒤에야 에러가 발생한다.
java.lang.ClassCastException: class java.lang.Integer cannot be cast to class java.lang.String (java.lang.Integer and java.lang.String are in module java.base of loader 'bootstrap')
List<Object>
제네릭의 경우
public class Item26Test {
@Test
public void rawTypeTest() {
List<String> strings = new ArrayList<>();
unsafeAdd(strings, Integer.valueOf(42));
String s = strings.get(0);
System.out.println("strings = " + s);
}
private void unsafeAdd(List<Object> list, Object o) {
list.add(o);
}
}
- 컴파일 전부터
List<Object>
를 넘겨야 하는 곳에List<String>
을 넘기고 있다고 에러가 난다.
java: incompatible types: java.util.List<java.lang.String> cannot be converted to java.util.List<java.lang.Object>
모든 타입을 받을 수 있는 비 한정적 와일드카드 타입
- 모든 타입을 받기 위해 로타입을 사용하고 싶은 유혹에 빠질 수 있다.
- 이럴 때 대신 사용할 수 있는게 비 한정적 와일드카드 타입이다.
private void unsafeAdd(List<?> list, Object o) {
list.add(o); // 컴파일 에러! null 외에 넣을 수 없다!
}
- 비 한정적 와일드타입에는
null
외에 어떤 원소도 넣을 수 없다.- 이 제약에서 벗어나고 싶다면
? extends 클래스
와 같이 한정적 와일드카드 타입을 이용해 어떤 클래스의 하위 클래스를 받을 것인지 명시하면 된다.
- 이 제약에서 벗어나고 싶다면
로 타입을 쓸 수 밖에 없을 때 1: class
리터럴을 사용할 때
- 딱 두가지 경우에 로 타입을 쓸 수 밖에 없는데,
class
리터럴에는 로 타입을 써야 한다. - 자바 명세는
class
리터럴에 매개변수화 타입을 사용하지 못하게 했다. (배열과 기본 타입은 허용한다.) List.class
,String[].class
,int.class
는 허용하고,List<String>.class
와List<?>.class
는 허용하지 않는다.
로 타입을 쓸 수 밖에 없을 때 2: instanceof
연산자를 사용할 때
- 런타임에는 제네릭 정보가 지워져서
instanceof
연산자는 비 한정적 와일드카드 타입 이외의 매개변수화 타입에는 적용할 수 없다. - 로 타입과 비한정적 와일드카드 타입의
instanceof
는 완전히 똑같이 동작한다.
if (o instanceof Set) { // 로 타입
Set<?> s = (Set<?>) o; // 와일드카드 타입
...
}
o
의 타입이Set
임을 확인한 다음 와일드카드 타입인Set<?>
로 형변환해야 한다. (로타입인Set
이 아님에 주의하자.)- 이는
검사 형변환(checked cast)
이므로 컴파일러 경고가 뜨지 않는다.
핵심 정리
- 로타입을 사용하면 런타임에 예외가 일어날 수 있으니 주의해야 한다.
- 로 타입은 제네릭이 도입되기 전 코드들과의 호환성을 지키기 위한 도구 중 하나일 뿐이다.
Set<Object>
는 어떤 타입의 객체도 저장할 수 있는 매개변수화 타입이다.Set<?>
는 모종의 타입 객체만 저장할 수 있는 와일드카드 타입이다.Set
은 제네릭 타입 시스템에 속하지 않는다.Set<Object>
,Set<?>
는 안전하지만, 로타입인Set
은 안전하지 않다.
반응형
'Java > 이펙티브 자바' 카테고리의 다른 글
이펙티브 자바, 쉽게 정리하기 - item 28. 배열보다는 리스트를 사용하라 (0) | 2022.01.09 |
---|---|
이펙티브 자바, 쉽게 정리하기 - item 27. 비검사 경고를 제거하라 (0) | 2022.01.09 |
이펙티브 자바, 쉽게 정리하기 - item 25. 톱 레벨 클래스는 한 파일에 하나만 담으라 (0) | 2022.01.09 |
이펙티브 자바, 쉽게 정리하기 - item 24. 멤버 클래스는 되도록 static으로 만들라 (0) | 2022.01.09 |
이펙티브 자바, 쉽게 정리하기 - item 23. 태그 달린 클래스보다는 계층구조를 활용하라 (0) | 2022.01.09 |