1. 용어정리
public class<T> Example{
private T member;
}
- 제네릭 클래스[인터페이스] : 클래스[인터페이스] 선언에 타입 매개변수(type parameter)가 쓰인다.
Example.class
- 제네릭 타입(Generic Type) : 제네릭 클래스와 제네릭 인터페이스를 통틀어 이르는 말.
Example<T>
- 매개변수화 타입(parameterized Type) : 각각의 제네릭 타입은 parameterizedType을 선언함.
Example<String>
- 타입 매개 변수(Type parameter) : 제네릭 선언에 사용된 매개변수
<T>
- formalType :
Example<E>
- actualType :
Example<String>
- 로타입(raw type) : 제네릭 타입에서 타입 매개변수를 전혀 사용하지 않았을 때.
Example
로타입은 제네릭 전후 코드의 호환을 위한 것이며, 동작하지만 좋은 예는 아니다.
2. 제네릭의 타입 안정성
오류는 가능한 한 발생 즉시, 이상적으로는 컴파일 타임에 발견하는 것이 좋다.
a. 로타입의 단점 : 컴파일 타임에 타입 정보를 알지 못한다.
ClassCastException과 같은 런타임에야 알아챌 수 있는 에러를 만들기 쉽다.
실제로 아래 예제는, 로타입의 사용으로 인해 컴파일 타임에는 문제가 없으나, 실제 런타임에는 ClassCastException이 터진다.
Integer값을 String값으로 캐스팅하려 했기 때문이다.
public class Raw {
public static void main(String[] args) {
List<String> strings = new ArrayList<>();
unsafeAdd(strings, Integer.valueOf(42));
String s = strings.get(0); // 컴파일러가 자동으로 형변환 코드를 넣어준다.
}
private static void unsafeAdd(List list, Object o) {
list.add(o);
}
}
b. 제네릭의 장점 : 컴파일 타임에 타입 선언이 녹아든다.
컴파일러가 타입선언에 대해 인지하고 있기 때문에, 아무런 경고 없이 컴파일되면 의도대로 동작할 것임을 보장한다.
public class Raw {
public static void main(String[] args) {
List<String> strings = new ArrayList<>();
unsafeAdd(strings, "42"); // 컴파일 타임에 에러를 체크해주어서 바꿀 수 있다.
String s = strings.get(0);
}
private static <T> void unsafeAdd(List<T> list, T o) { // 제네릭 선언
list.add(o);
}
}
컴파일러가, 컬렉션에서 원소를 넣는 모든 곳에 보이지 않는 형변환을 추가하여 절대 실패하지 않음을 보장하였다.
++ 추가 내용
3. 로타입은 절대로 사용하지 말자.
제네릭이 안겨주는 안정성과 표현력을 모두 잃게된다.
a. 로타입이 만들어진 이유 - 호환성
기존 코드를 모두 수용하면서 제네릭을 사용하는 새로운 코드와 맞물려돌아가게 하기 위해서이다.
호환성 : 로타입 지원 + 제네릭 구현시 소거(erasure) 방식을 이용
List
vs List<Object>
List
: 로타입 - 제네릭타입에서 완전 발은 떼었다.List<Object>
: 임의 객체를 허용하는 매개변수화 타입 - 타입에 대한 정보를 컴파일러에 알려주었다.- 제네릭의 하위타입 규칙 :
List<String>
은 로타입인List
의 하위타입이지만,List<Object>
의 하위타입은 아니다.
로타입을 사용하면 타입 안정성을 잃게된다.
4. 타입 안전 + 유연 : 비한정 와일드카드 타입
비한정 와일드카드 타입(unbounded wildcard type) : 제네릭 타입을 쓰고 싶지만, 실제 타입 매개변수가 무엇인지 신경쓰고 싶지 않을 때 사용한다. Set<E>
의 비한정 와일드카드 타입은 Set<?>
static int numElementsInCommon(Set<?> s1, Set<?> s2){...}
와일드카드 타입은 안전하고 로타입은 안전하지 않다.
- 로타입 : 아무 원소나 넣을 수 있어 타입 불변식을 훼손하지 쉽다.
- 와일드카드 타입 : Collection<?>에는 어떤 원소도 넣을 수 없다. (null 외에는)
컴파일 타임에 오류메세지를 볼 수 있다.
컬렉션의 타입 불변식을 훼손하지 못하게 막고, 컬렉션에서 꺼낼 수 있는 객체의 타입도 알수 없게 한다.
5. 로타입의 규칙예외
a. class 리터럴에는 로타입을 사용한다
자바 명세 자체가 class 리터럴에 매개변수화 타입을 사용하지 못하게 하였다.
- 허용 :
List.class
,String[].class
,int.class
- 불가 :
List<String>.class
,List<?>.class
b. Instanceof 연산자는 로타입을 사용한다.
instanceof 연산자는 비한정 와일드카드 타입 이외의 매개변수화 타입에는 적용할 수 없다.
로타입이든 비한정적 와일드카드 타입이든 instanceof는 똑같이 동작한다. : <?> 코드만 지저분해지므로 로타입
런타임에는 제네릭 타입정보가 지워진다.
if(o instanceof Set){ // 로타입
Set<?> s = (Set<?>) o; // 와일드카드 타입
}
6. 용어정리
한글 | 영문 | 예 |
---|---|---|
매개변수화 타입 | parameterized type | List<String> |
실제 타입 매개변수 | actual type parameter | String |
제네릭 타입 | generic type | List<E> |
정규 타입 매개변수 | formal type parameter | E |
비한정적 와일드카드 타입 | unbounded wildcard type | List<?> |
로 타입 | raw type | List |
한정적 타입 매개변수 | bounded type parameter | <E extends Number> |
재귀적 타입 한정 | recursive type bound | <T extends Comparable<T>> |
한정적 와일드카드 타입 | Bounded wildcard type | <? extends Number> |
제네릭 메서드 | generic method | static <E> List<E> asList(E[] a) |
타입 토큰 | type token | String.class |
'Dev Book Review > Effective Java' 카테고리의 다른 글
[Effective Java] item28. 배열보다는 리스트를 사용하라 (0) | 2020.05.19 |
---|---|
[Effective Java] item 27. 비검사 경고를 제거하라 (0) | 2020.05.19 |
[Effective Java] Chapter 4: 클래스와 인터페이스 (0) | 2020.05.05 |
[Effective Java] item25. 톱레벨 클래스는 한 파일에 하나만 담으라 (0) | 2020.05.05 |
[Effective Java] item24. 멤버 클래스는 되도록 static으로 만들어라용도로만 사용하라 (0) | 2020.05.05 |