[Java] Item 26 : 로 타입은 사용하지 말라
Effective Java를 읽고 정리한 정리본입니다.
📌 Item 26 : 로 타입은 사용하지 말라
🫧 제네릭 (Generics)
: 클래스 내부에서 사용할 데이터 타입을 외부에서 지정하는 기법
제네릭 클래스와 제네릭 인터페이스를 통틀어 지칭하는 말
여기서 제네릭 클래스 혹은 제네릭 인터페이스란 클래스와 인터페이스 선언에 타입 매개변수가 쓰이는 클래스 혹은 인터페이스를 지칭한다.
예를 들면 List 인터페이스는 원소의 타입을 나타내는 타입 매개변수 E를 받는다. (List
class FruitBox<T> {
List<T> fruits = new ArrayList<>();
public void add(T fruit) {
fruits.add(fruit);
}
}
이 예시 코드에서는 FruitBox 클래스명 옆에
여기서 의문이 생길 수 있다. 아까 전 설명에는 List 인터페이스가 타입 매개변수 E를 받는다고 하였는데, 예시에서는 T로 표현하였다.
이는 타입 파라미터 기호 네이밍에 따라 달라지는 것으로, 정해진 식별자 기호가 있는 것은 아니나, 관습적으로 기호를 정해 사용한다.
✨ 타입 파라미터 기호 네이밍
타입 | 설명 |
---|---|
<T> |
타입(Type) |
<E> |
요소(Element), 예를 들어 List |
<K> |
키(Key), 예를 들어 Map<K, V> |
<V> |
리턴 값 또는 매핑된 값(Variable) |
<N> |
숫자(Number) |
<S, U, V> |
2번째, 3번째, 4번째에 선언된 타입 |
🫧 매개변수화 타입
각각의 제네릭 타입은 일련의 매개변수화 타입을 정의한다.
List<String> lst = new ArrayList<String>();
아까 전 타입 파라미터 기호를 사용해 제네릭을 정의했다면, 이제는 이것을 실체화할 차례이다.
예시와 같이 List
여기서 String이 정규 타입 매개변수 E에 해당하는 실제 타입 매개변수다.
🫧 로 타입 (raw type)
: 제네릭 타입에서 타입 매개변수를 전혀 사용하지 않을 때를 의미한다.
제네릭 타입을 하나 정의하면 그에 딸린 로 타입도 함께 정의된다. 제네릭 지원 전 만들어진 코드들은 모두 로 타입으로 작성되었기 때문에 호환성을 위해 로 타입이 여전히 존재한다고 할 수 있다.
ex) List
여기서 문제가 발생한다. 로 타입은 제네릭의 이점을 전혀 살리지 못한다.
제네릭의 의미 자체가 클래스 내부에서 사용할 데이터 타입을 외부에서 지정하는 기법인데, 데이터 타입 자체를 빼 버렸으니 다양한 문제가 발생하는 것이다.
제네릭으로 사용했을 시 컴파일러에서 오류를 잡는 반면, 로 타입으로 사용할 시 런타임 시점에 오류가 터지므로 로 타입은 사용해서는 안 된다.
✨ 로 타입과 Object 매개변수화 타입
List와 같은 로 타입은 사용해서는 안 되나, List
둘이 비슷하다고 느낄 수 있으나, 엄연히 다르다. List는 제네릭 타입에서 완전히 발을 뺀 것이고, List
예를 들면 다음과 같다.
매개변수로 List를 받는 메서드에 List
🫧 비한정적 와일드카드 타입
: 제네릭 타입을 쓰고 싶지만 실제 타입 매개변수가 무엇인지 신경 쓰고 싶지 않은 경우 사용
- <?>를 사용해서 나타낸다.
-> ex) Set의 비한정적 와일드카드 타입은 Set<?>이다.
그렇다면 로 타입과 비한정적 와일드카드 타입의 차이는 무엇일까?
✨ 로 타입과 비한정적 와일드카드 타입
로 타입 컬렉션에는 아무 원소나 넣을 수 있으므로 타입 불변식을 훼손하기 쉬운 방면, 비한정적 와일드카드 타입은 null 외 어떤 원소도 넣을 수 없다.
🫧 로 타입이 사용되는 예외 상황
그렇다면 이러한 로 타입은 기존 코드와의 호환성을 위해 남겨두는 것 외에는 전부 사용하지 않아야 할까?
답은 아니다.
로 타입이 불완전하고 위험한 것은 맞으나, 몇 가지 예외 상황에서는 로 타입을 사용하곤 한다.
다음은 로 타입이 사용되는 예외 상황이다.
- class 리터럴
- instanceof 연산자
✨ 클래스 리터럴
자바 명세에서는 class 리터럴에 매개변수화 타입을 사용하지 못하게 했다.
List.class, String[].class, int.class (O)
List.class, List<?>.class (X)
✨ instanceof 연산
런타임 시 제네릭 타입 정보가 지워지므로 instanceof 연산자는 비한정적 와일드카드 타입 이외의 매개변수화 타입에는 적용할 수 없다.
이것은 제네릭 구현 시 사용하는 “소거 방식” 중 하나로, 해당 내용은 뒤에 자세하게 설명되어 있다.
소거 방식 사용에 따라 로 타입이든 비한정적 와일드카드 타입이든 instanceof는 완전히 똑같이 동작하므로, 아무런 역할 없이 지저분한 코드를 추가할 바에는 로 타입을 쓰는 편이 깔끔하다.