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){...}
와일드카드 타입은 안전하고 로타입은 안전하지 않다.
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 |
댓글
Comments