#23 새로 작성하는 코드에서는 원천(raw) 타입을 사용하지 말자
Generics을 이용하면 어떤 타입의 객체를 허용할지 컴파일러에게 알려줘서 캐스트 코드를 컴파일러가 자동으로 만들어 줍니다.
그리고 잘못된 타입의 객체는 컴파일 시에 미리 알 수 있습니다.
따라서 런타임 시 발생할 수 있는 에러를 미리 대처할 수 있는 장점이 있습니다.
제네릭의 장점을 극대화시키고 복잡성을 최소화하는 방법에 대해 알아보겠습니다.
제목에서 말하는 원천(raw) 타입이란, 실 타입 매개 변수(ex String, Integer...)가 없이 사용되는 제네릭 타입을 말합니다.
아래와 같이 List<E>에 대한 원천 타입은 List입니다.
원천 타입을 이용하면 ClassCastException과 같은 런타임 에러가 발생할 수 있습니다.
이를 원천 타입이 아닌 매개변수화(parameterized) 타입으로 선언되면, 컴파일 시에 자동으로 잘못된 타입으로 변환을 미리 체크해 줄 수 있습니다.
어떤 타입이든 이용할 수 있다는 점에서 원천 타입 List와 List<Object>는 같아 보이지만, 차이가 있습니다.
List : 제네릭 타입의 검사가 생략
List<Object> : 어떤 타입의 객체도 저장할 수 있다는 것을 컴파일러에 명시적으로 알림
아래 코드는 원천 타입을 이용하고 있습니다.
제네릭 타입의 검사가 생략되므로 컴파일은 되지만, ClassCastException 에러가 발생합니다.
unsafeAdd 메서드의 인사를 List에서 List<Object>로 바꾸면 아래와 같습니다.
이때는 컴파일 에러가 발생합니다.
java: incompatible types: java.util.List<java.lang.String> cannot to converted to java.util.List<java.lang.Object>
말 그대로 List<String>을 List<Object>로 변환할 수 없다는 내용입니다.
String은 Object의 서브 클래스이기 때문에 형 변환이 가능할 거 같지만, List<String>는 List<Object>의 서브 타입이 아닙니다.
오히려 List<String>은 원천 타입인 List의 서브 타입입니다.
List나 List<Object> 둘 다 문제가 발생할 수 있습니다.
컬렉션의 요소 타입이 미지정 또는 어떤 타입이든 상관없다면 언바운드 와일드카드 타입(unbounded wildcard type)을 이용할 수 있습니다.
예를 들어, 두 개의 Set 모두에 들어있는 요소의 개수를 반환하는 메서드를 원천 타입을 이용해서 구현할 수 있습니다.
문제없이 잘 동작하지만 원천 타입이기 때문에 수정을 하다가 문제가 발생할 수 도 있습니다.
실 타입 매개변수를 모르거나, 어떤 타입이든 상관없다면 물음표(?)를 이용할 수 있습니다.
언바운드 와일드카드 타입이라 부르는데, 이는 어떤 타입의 객체도 이용할 수 있다는 의미입니다.
원천 타입보다 언바운드 와일드카드 타입은 안정성 측면에서 더 나은 점이 있습니다.
하지만 아래와 같이 Collection<?>에는 어떤 타입의 요소 건(not null) 마음대로 추가할 수 없습니다.
컬렉션 타입 불변성을 깨지 못하게 컴파일러가 에러로 처리하는 건데, 결국 read만 할 수 있게 됩니다.
이를 해결하기 위해 제네릭 메서드(generic method)나 바운드 와일드카드 타입(bounded wildcard type)을 이용할 수 있는데 나중에 다루겠습니다.
결론은 원천 타입을 이용하면 런타임 에러가 발생할 수 있으므로, 이용해서는 안됩니다.
(원천 타입은 제네릭이 나오기 전에 생긴 기존 코드와 호환성을 위해 존재함.)
Set : 원천 타입, 제네릭 타입 시스템 이용 X, 안정성 보장 X
Set<Object> : 매개 변수화 타입, 어떤 타입의 객체도 저장할 수 있음. 안정성 보장 O
Set<?> : 언바운드 와일드카드 타입, 미 지정 or 어떤 타입의 객체도 저장할 수 있음. 안정성 보장 O