backend/Java

[Effective Java] 23. 새로 작성하는 코드에서는 원천(raw) 타입을 사용하지 말자.

버리야 2009. 10. 30. 10:14
반응형

[Effective Java] 23. 새로 작성하는 코드에서는 원천(raw) 타입을 사용하지 말자.
 
제네릭을 사용하면 각 컬렉션에 어떤 타입의 객체를 허용할 것인지 컴파일러에게 알려주게 되며, 캐스트 코드를 컴파일러가 자동으로 만들어준다. 또한 잘못된 타입의 객체를 추가하려고 하면 컴파일 시에 알려준다.
 
하나 이상의 타입 매개변수(type parameter)를 선언하고 있는 클래스나 인터페이스를 제네릭 클래스, 또는 제네릭 인터페이스라고 한다.
자바 1.5를 기준으로 List 인터페이스에는 하나의 타입 매개변수로 E가 있는데, 여기서 E는 List에 저장되는 요소의 타입을 나타낸다.
 

각 제네릭 타입에서는 원천(raw) 타입을 정의하는데 원천 타입이란 실 타입 매개변수가 없이 사용되는 제네릭 타입의 이름을 말한다.
예를 들어, List<E>에 대한 원천 타입은 List로써, 마치 타입 선언에서 제네릭 타입 정보가 지워진 것처럼 동작하는데, 실제로는 제네릭이 추가되기 전에 인터페이스 타입 List가 했던 것과 같은 방법으로 동작한다.
 
앞으로 작성하는 새 코드에 List와 같은 원천 타입을 사용하면 안 되지만, List<Object>처럼 어떤 객체도 List에 추가 할 수 있는 매개변수화 타입은 사용해도 좋다. 그렇다면, 원천 타입인 List와 매개변수화 타입인 List<Object>의 차이는 무엇일까? 전자는 제네릭 타입의 검사가 생략되는 반면, 후자는 어떤 타입의 객체도 저장할 수 있다는 것을 컴파일러에게 명시적으로 알려주려는 것이다. 따라서 List<String> 타입은 List의 매개변수로 전달할 수 있지만,  List<Object>의 매개변수로는 전달할 수 없다. 제네릭에는 서브 타입 규칙이 있어서 List<String>은 원천 타입인 List의 서브타입이지만 매개변수 타입인 List<Object>의 서브타입은 아니다.
그런이유로, List와 같은 원천 타입을 사용하면 타입의 안전성을 상실하게 되지만, List<Object>과 같은 매개변수 타입을 사용하면 그렇지 않다.
 
타입 안전의 대안으로 언바운드 와일드 카드(unbound wildcard type)을 제공하고 있다. 제네릭 타입은 사용하고 싶지만 실 타입 매개변수를 모르거나, 어떤 타입이든 관계없다면 타입 대신 물음표(?)를 사용하면 된다. 예를 들어 제네릭 타입 Set<E>의 언바운드 와일드 카드 타입은 Set<?>.
이것은 어떤 타입의 객체(Set포함)도 저장할 수 있는 가장 보편화된 매개변수화 Set 타입이다.
 
언바운드 와일드 카드 타입인 Set<?>과 원천 타입인 Set의 차이는 무엇일까? 와일드 카드 타입은 안전하고 원천 타입은 그렇지 않다.
원천 타입의 컬렉션에는 어떤 타입의 요소도 추가할 수 있지만, 해당 컬렉션의 타입불변성을 쉽게 무너뜨릴 수 있다.
 
향후에 작성하는 새 코드에는 원천 타입을 사용하지 않는다는 규칙에도 두 가지 예외가 있는데, 런타임 시에는 제네릭 타입의 정보가 없어진다는 것 때문에 가능하다.
첫번째로, 원천 타입은 클래스 리터럴(class literal)의 형태로 사용해야 한다. List.class, String[].class, int.class는 모두 적법하지만, List<String>.class, List<?>.class는 허용되지 않는다.
두번째로, instanceof 연산자와 관련. 제네리 타입의 정보는 런타임시에 없어지므로 언바운드 와일드 카드 타입이 아닌 다른 매개변수화 타입에 대해 instanceof 연산자를 사용할 수 없다. 원천 타입 대신 언바운드 와일드 카드 타입을 사용할 때는 instanceof 연산자의 동작에 영향을 주지 않으며, 이 경우 <>와 물음표(?)는 아무 의미가 없다. 제네릭 타입과 함께 instanceof 연산자를 사용할 때는 다음과 같이..
 
// Legitimate use of raw type - instanceof operator
if (o instanceof Set) { // Raw type
Set<?> m = (Set<?>) o; // Wildcard type
...
}
 
o가 Set타입이라는 것이 결정되면, o의 타입을 원천타입(Set)이 아닌 언바운드 와일드 카드 타입(Set<?>)으로 캐스팅해야 한다. 그리고 이런 캐스팅은 타입을 확인하고 하는 것이므로 컴파일 시 경고 메시지가 나오지 않을 것이다.


반응형