-
26. 로(Raw) 타입은 사용하지 말라JAVA/Effective java 2021. 3. 11. 13:45
- 클래스와 인터페이스 선언에 타입 매개변수가 쓰이면, 이를 제네릭 클래스 혹은 제네릭 인터페이스라 한다.
- List 인터페이스는 원소의 타입을 나타내는 타입 매개변수 E를 받는다.
- 그래서 이 인터페이스의 완전한 이름은 List <E>이지만, 짧게 List라고 쓴다.
- 이러한 제네릭 클래스와 제네릭 인터페이스를 통틀어 제네릭 타입이라 한다.
- List<String>을 예로 이는 원소의 타입이 String인 리스트를 뜻하는 매개변수화 타입이다.
- 여기서 String이 정규 타입 매개변수 E에 해당하는 실제 타입 매개변수이다.
- 제네릭 타입을 하나 정의하면 그에 딸린 로 타입(raw type)도 함께 정의된다.
- 로 타입이란 제네릭 타입에서 타입 매개변수를 전혀 사용하지 않을 때를 말한다.
- 따라서 List<E>의 로 타입은 List이다. 로 타입은 타입 선언에서 제네릭 타입 정보가 전부 지워진 것처럼 동작하는데, 제네릭이 도래하기 전 코드와 호환되도록 하기 위한 궁여지책이라 할 수 있다.
컬렉션의 로 타입
- 이 코드를 사용하면 실수로 Stamp 대신 동전(Coin)을 넣어도 아무 오류 없이 컴파일되고 실행된다.
- 오류는 가능한 한 발생 즉시, 이상적으로는 컴파일할 때 발견하는 것이 좋다.
- 이 예는 오류가 발생하고 한참 뒤인 런타임에야 알아챌 수 있는데, 이렇게 되면 런타임에 문제를 겪는 코드와 원인을 제공한 코드가 물리적으로 상당히 떨어져 있을 가능성이 커진다.
- ClassCastException이 발생하면 stamps에 동전을 넣은 지점을 찾기 위해 코드 전체를 훑어봐야 할 수도 있다.
- 제네릭을 활용하면 이 정보가 주석이 아닌 타입 선언 자체에 녹아든다.
- 이렇게 선언하면 컴파일러는 stamps 에는 Stamp의 인스턴스만 넣어야 함을 컴파일러가 인지하게 된다.
- 따라서 아무런 경고 없이 컴파일된다면 의도대로 동작할 것임을 보장한다.
- 컴파일러는 컬렉션에서 원소를 꺼내는 모든 곳에 보이지 않는 형 변환을 추가하여 절대 실패하지 않음을 보장한다.
- 따라서 로 타입을 써서 제네릭이 안겨주는 안정성과 표현력을 모두 잃는 일은 하지 말자.
- 제네릭을 받아들이기까지 수년이 걸렸고 제네릭 없이 짠 코드를 모두 수용하기 위해 로 타입을 지원한 것뿐이다.
List와 List<Object>
- List와 같은 로 타입은 사용해서는 안 되나, List <Object>처럼 임의 객체를 허용하는 매개변수화 타입은 괜찮다.
- 로 타입인 List와 매개변수화 타입인 List <Object>의 차이는 무엇일까?
- List는 제네릭 타입에서 완전히 발을 뺀 것이고, List <Object>는 모든 타입을 허용한다는 의사를 컴파일러에 명확히 전달한 것이다.
- 매개변수로 List를 받는 메서드에 List <String>을 넘길 수 있지만, List <Object>를 받는 메서드에는 넘길 수 없다.
- 이는 제네릭의 하위 타입 규칙 때문인데List <String>은 로 타입인 List의 하위 타입이지만, List <Object>의 하위 타입은 아니다.
- 다음의 예는 컴파일은 되지만 로 타입인 List를 사용하여 unchecked call이 발생하고 strings.get(0)의 결과를 형 변환하려 할 때 ClassCastException을 던진다.
- 이 형 변환은 컴파일러가 자동으로 만들어준 것이라 보통은 실패하지 않는다. 하지만 이 경우엔 컴파일러의 경고를 무시하여 그 대가를 치루었다.
- 이제 로 타입인 List를 매개변수화 타입인 List <Object>로 바꾼 다음 다시 컴파일해보자.
- 이번에는 다음과 같은 오류 메시지가 출력되며 컴파일조차 되지 않는다.
와일드카드 타입
- 제네릭 타입을 쓰고 싶지만 실제 타입 매개변수가 무엇인지 신경 쓰고 싶지 않다면 물음표(?)를 사용하자.
- 제네릭 타입인 Set <E>의 비한정적 와일드카드 타입은 Set <?>이다.
- 이것이 어떤 타입이라도 담을 수 있는 가장 범용적인 매개변수화 Set 타입이다.
- 다음은 비한정적인 와일드카드 타입을 사용하여 numElementsInCommon을 선언한 모습이다.
- Set <?>와 로 타입인 Set의 차이는 무엇일까
- 특징을 간단히 말하면 와일드카드 타입은 안전하고, 로 타입은 안전하지 않다.
- 로 타입 컬렉션에는 아무 원소나 넣을 수 있으니 타입 불변식을 훼손하기 쉽다.
로 타입의 소소한 예외
1. class 리터럴에는 로 타입을 써야 한다.
- 자바 명세는 class 리터럴에 매개변수화 타입을 사용하지 못하게 했다. ( 배열과 기본 타입은 허용한다. )
- 예를 들어 List.class, String[].class, int.class 는 허용하고 List<String>.class 와 List<?>.class 는 허용하지 않는다.
2. instanceof 연산자는 로 타입을 써도 좋다.
- 런타임에는 제네릭 타입 정보가 지워지므로 instanceof 연산자는 비한정적 와일드카드 타입 이외의 매개변수화 타입에는 적용할 수 없다.
- 와일드카드 타입의 꺽쇠괄호와 물음표는 아무런 역할 없이 코드만 지저분하게 만드므로 로타입을 쓰는 편이 깔끔하다.
- 다음은 제네릭 타입에 instanceof를 사용하는 올바른 예다.
핵심 정리
- 로 타입을 사용하면 런타임에 예외가 일어날 수 있으니 사용하면 안된다.
- 로 타입은 제네릭이 도입되기 이전 코드와의 호환성을 위해 제공될 뿐이다.
- Set<Object>는 어떤 타입의 객체도 저장할 수 있는 매개변수화 타입이고, Set<?>는 모종의 타입 객체만 저장할 수 있는 와일드카드 타입이다.
- 그리고 이들의 로 타입인 Set은 제네릭 타입 시스템에 속하지 않는다.
- Set<Object>와 Set<?>는 안전하지만, 로 타입인 Set은 안전하지 않다.
참고 자료
'JAVA > Effective java' 카테고리의 다른 글
28. 배열보다는 리스트를 사용하라 (0) 2021.03.13 27. 비검사 경고를 제거하라. (0) 2021.03.12 25. 톱레벨 클래스는 한 파일에 하나만 담으라 (0) 2021.03.09 24. 멤버 클래스는 되도록 static으로 만들라 (0) 2021.03.08 23. 태그 달린 클래스보다는 클래스 계층구조를 활용하라 (0) 2021.03.07