티스토리 뷰
26. 로 타입은 사용하지 말라
갑자기 로 타입이라고 하길래 이 친구가 무슨 녀석인지 부터 알 필요가 있다.
쉽게 말해 제네릭 타입 List<E>의 로(raw) 타입은 List가 된다.
클래스의 매개 변수를 제네릭을 통해 특정 클래스로 제안하지 않으면 발생할 문제들이 눈에 선하다
제네릭이 생기까지는 10년의 세월이 흘러 기존의 코드와의 호환성을 위해
raw 타입을 남겨뒀다고한다.
// Stamp 클래스만 취급합니당 ^^7
private final Collection stamps = ...;
stamps.add(new Coin());
위와 같은 Collection을 raw 타입으로 선언하고 주석으로 열심히 Stamp 객체만 달라고 해도 무시하고 Coin을 넣는다.
컴파일러는 경고를 unchecked call 경고를 뱉지만 이것도 무시하기로 한다.
for (Iterator i = stamps.iterator(); i.hasNext();){
Stamp stamp = (Stamp) i.next();
...
}
위와 같은 코드를 작성한다고 했을 때 우리는 런타임에서야 ClassCastException을 확인할 수 있다. (물론 Stamp는 Coin의 상위 타입이 아니다)
이를 컴파일 타임에 알 수 있었다면 좋았을 것이다.
private final Collection<Stamp> stamps = ...;
위와 같이 Collection에서 취급할 클래스를 제네릭을 통해 명시 해주면 stamps에 Coin을 넣으려고 하면 이를 컴파일 타임에 알 수 있게 된다.
List와 같은 raw 타입은 안되지만 List<Object>와 같은 임의 객체 허용 타입은 괜찮다.
이 둘의 차이는 무엇일까? 바로 List를 parameter로 받는 상황에서 차이가 생긴다.
// 코드 26-4 런타임에 실패한다. - unsafeAdd 메서드가 로 타입(List)을 사용 (156-157쪽)
public class Raw {
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);
}
}
위의 코드와 같이 raw 타입의 parameter를 받으면 Integer가 들어간 List에서 Integer를 String으로 type casting할 때 ClassCastException이 발생한다.
public class Raw {
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<Object> list, Object o) {
list.add(o);
}
}
코드를 위와 같이 Object 제네릭 타입으로 parameter를 받으면 List<String>을 넣는 순간에 컴파일 에러가 발생한다.
이 이유가 또 재밌는데 모든 제네릭 List 타입들은 raw 타입인 List의 하위 타입이 된다. 따라서 List<String> -> List로 타입 캐스팅은 가능하지만 List<String> -> List<Object>는 불가능 하다. 이는 List<String>과 List<Object>가 각각 List를 상속 받은 형제 관계 클래스라고 생각하면 쉽다. 형제 관계에서는 타입 캐스팅이 불가능하다. 이와 같은 관계를 불공변이라고한다. 신기하게 배열은 공변이라 Integer[] -> Object[]로 타입 캐스팅이 가능하다.
그렇다면 모든 것을 다 받는 클래스를 설계하고 싶을 땐 어떻게 하면 좋을까? List<?>와 같이 와일드카드 타입을 사용하면된다.
물론 그냥 ?만 쓰면 null을 제외한 어떤 클래스도 추가할 수 없다.
와일드 카드는 any가 아닌 unknown 타입으로 취급된다. 따라서 어떤 클래스를 받을 지를 고르기 위해선 와일드 카드의 상한과 하한을 정해줘야한다. 이는 아이템 31 한정적 와일드 카드에서 배울 수 있다.
raw 타입은 사용해선 안된다고 했지만 예외가 있다. class 리터럴에는 raw 타입을 써야한다.
배열과 premetive 타입을 제외하고 class 리터럴은 parametric 타입을 허용하지 않는다. ex) String[].class, int.class 됨 // List<String>.class 안 됨
두번째로는 instanceof 연산자에서는 raw 타입을 써야한다. 제네릭이나 와일드카드 같은 경우는 컴파일 타임에서 에러를 잡기 위함이라 런타임에서는 제네릭 타입의 정보가 지워진다.
if (o instanceof Set){ // 그냥 raw를 쓰자
...
}
따라서 이러한 경우엔 제네릭, 와일드카드는 raw 타입과 똑같이 동작하므로 코드가 간단한 raw 타입을 쓰자.
'Book > Effective Java' 카테고리의 다른 글
Effective Java - Item 32. 제네릭과 가변인수를 함께 쓸 때는 신중하라 (0) | 2023.05.22 |
---|---|
Effective Java - Item 28. 배열보다는 리스트를 사용하라. (1) | 2023.05.22 |
Effective Java - Item 24. 멤버 클래스는 되도록 static으로 만들어라 (0) | 2023.03.22 |
Effective Java - item 19. 상속을 고려해 설계하고 문서화하라. 그러지 않았다면 상속을 금지하라 (0) | 2023.03.22 |
Effective Java - item 16. public 클래스에서는 public 필드가 아닌 접근자 메서드를 사용하라 (0) | 2023.03.22 |