용어 정리
- 제네릭 클래스/제네릭 인터페이스: 클래스/인터페이스 선언에 타입 매개변수(type parameter)가 쓰인 것
- 제네릭 타입(generic type): 제네릭 클래스와 제네릭 인터페이스를 통틀어 칭함
- 매개변수화 타입(parameterized type): e.g.) List<String>은 원소의 타입이 String인 리스트를 뜻하는 매개변수화 타입
여기서 String이 정규(formal) 타입 매개변수 E에 해당하는 실제(actual) 타입 매개변수 - 로 타입(raw type): 제네릭 타입에서 타입 매개변수를 전혀 사용하지 않을 때 e.g.) List<E>의 로 타입은 List
자바에 제네릭 기능이 생기기 전 코드와 호환되도록 하기 위한 궁여지책
한글 용어 | 영문 용어 | 예 | 아이템 |
매개변수화 타입 | parameterized type | List<String> | 아이템26 |
실제 타입 매개변수 | actual type parameter | String | 아이템26 |
제네릭 타입 | generic type | List<E> | 아이템26, 아이템 29 |
정규 타입 매개변수 | formal type parameter | E | 아이템26 |
비한정적 와일드카드 타입 | unbounded wildcard type | List<?> | 아이템26 |
로 타입 | raw type | List | 아이템26 |
한정적 타입 매개변수 | bounded type parameter | <E extends Number> | 아이템29 |
재귀적 타입 한정 | recursive type bound | <T extends Comparable<T>> | 아이템30 |
한정적 와일드카드 타입 | bounded wildcard type | List<? extends Number> | 아이템31 |
제네릭 메서드 | generic method | static <E> List<E> asList(E[] a) | 아이템30 |
타입 토큰 | type token | String.class | 아이템33 |
[Why]
- 로 타입을 쓰면 제네릭이 안겨주는 안전성과 표현력을 모두 잃게 된다.
// Stamp 인스턴스만 취급하려는 의도의 raw type
private final Collection stamps = ...;
// 실수로 동전을 넣는다.
stamps.add(new Coin(...)); // "unchecked call" 경고를 내뱉는다.
오류 없이 컴파일되고 실행된다.
for (Iterator i = stamps.iterator(); i.hasNext(); ) {
Stamp stamp = (Stamp) i.next(); // ClassCastException을 던진다.
stamp.cancel();
}
컬렉션에서 이 동전을 다시 꺼내기 전에는 오류를 알아채지 못한다.
- 로 타입은 제네릭 등장 이전의 자바 코드와의 호환성 때문에 있는 것이다.
[How]
// 매개변수화된 컬렉션 타입 - 타입 안전성 확보!
private final Collection<Stamp> stamps = ...;
컴파일러는 컬렉션에서 원소를 꺼내는 모든 곳에 보이지 않는 형변환을 추가하여 절대 실패하지 않음을 보장한다.
List<String> strings = List.of("1", "2");
List list1 = strings; // 가능
List<Object> list2 = strings; // 불가능
- List 같은 로 타입은 사용해서는 안 되나, List<Object>처럼 임의 객체를 허용하는 매개변수화 타입은 괜찮다.
- List는 제네릭 타입에서 완전히 발을 뺀 것이고, List<Object>는 모든 타입을 허용한다는 의사를 컴파일러에게 전달한 것이다.
- List<String>: List를 받는 곳에는 넘길 수 있지만, List<Object>를 받는 곳에는 넘길 수 없다.
- 제네릭의 하위 타입 규칙 때문인데, List<String>은 로 타입인 List의 하위 타입이지만 List<Object>의 하위 타입은 아니다. (아이템28)
- 원소의 타입을 몰라도 되는 로 타입을 쓰고 싶을 때는 비한정적 와일드카드 타입(unbounded wildcard type)을 대신 사용하자.
- 비한정적 와일드카드 타입인 Set<?>와 로 타입인 Set의 차이는 무엇일까?
- 로 타입 컬렉션에는 아무 원소나 넣을 수 있으니 타입 불변식을 훼손하기 쉽다.
- 반면, Collection<?>에는 null 외에는 어떤 원소도 넣을 수 없다. 어쨌든 컬렉션의 타입 불변식을 훼손하지 못하게 막는다.
- 이러한 제약을 받아들일 수 없다면 제네릭 메서드(아이템30)나 한정적 와일드카드 타입(아이템31)을 사용하면 된다.
예외
- class 리터럴에는 로 타입을 써야 한다.
- 자바 명세는 class 리터럴에 매개변수화 타입을 사용하지 못하게 했다(배열과 기본 타입은 허용한다)
- 예를 들어 List.class, String[].class, int.class는 허용하고 List<String>.class, List<?>.class는 허용하지 않는다.
- 런타임에는 제네릭 타입 정보가 지워지므로 instanceof 연산자는 비한정적 와일드카드 타입 이외의 매개변수화 타입에는 적용할 수 없다. 그리고 로 타입이든 비한정적 와일드카드 타입이든 instanceof는 완전히 똑같이 동작한다.
if (o instanceof Set) { // 로 타입
Set<?> s = (Set<?>) o; // 와일드카드 타입
...
}
o의 타입이 Set임을 확인한 다음 와일드카드 타입인 Set<?>로 형변환해야 한다(로 타입인 Set이 아니다). 이는 형변환(checked cast)이므로 컴파일러 경고가 뜨지 않는다.
'독서찰기(讀書札記) > 이펙티브 자바' 카테고리의 다른 글
[아이템 28] 배열보다는 리스트를 사용하라 (0) | 2022.02.07 |
---|---|
[아이템 27] 비검사 경고를 제거하라 (0) | 2022.02.07 |
[아이템 25] 톱레벨 클래스는 한 파일에 하나만 담으라 (0) | 2022.02.05 |
[아이템 24] 멤버 클래스는 되도록 static으로 만들라 (0) | 2022.02.05 |
[아이템 23] 태그 달린 클래스보다는 클래스 계층구조를 활용하라 (0) | 2022.02.05 |