■ 배열과 제네릭 차이점
1. 배열은 공변이다. Sub가 Super의 하위 타입이라면 배열 Sub[]는 배열 Super[]의 하위 타입이다. 즉, 함께 변한다는 뜻이다. 반면에 제네릭은 불공변이다. List<Type1>은 List<Type2>의 하위 타입도 아니고 상위 타입도 아니다. 제네릭에 문제가 있다고 생각할 수 있지만 사실 문제가 있는건 배열이다.
리스트를 사용할 경우 컴파일 때 에러를 바로 알 수 있다.
// 런타임 실패
Object[] objectArray = new Long[1];
objectArray[0] = "타입이 달라 넣을 수 없다."; // ArrayStoreException
// 다음 코드는 문법에 맞지 않아 컴파일조차 되지 않음
List<Object> ol = new ArrayList<Long>(); // 호환되지 않는 타입이다
ol.add("타입이 달라 넣을 수 없다.");
2. 배열은 실체화(reify)된다. 즉, 배열은 런타임에도 자신이 담기로 한 원소의 타입을 인지하고 확인한다. Long 배열에 String을 넣으려고하면 ArrayStoreException이 발생한다. 반면에 제네릭은 타입 정보가 런타임에 소거된다. 따라서 런타임시에는 어떠한 타입도 들어올 수 있다. 하지만 컴파일 타임에서 이를 걸러내기 때문에 안전한다. 원소 타입을 컴파일타임에만 검사하며 런타임에는 알수조차 없다는 뜻이다.
이러한 차이점으로 배열과 제네릭은 잘 어우러지지 못한다. 배열은 제네릭 타입, 매개변수화 타입, 타입 매개변수로 사용할 수 없다.
■ 제네릭 배열을 만들지 못하게 막은 이유
타입이 안전하지 않기 때문이다. 만약 허용한다면 컴파일러가 자동 생성한 형 변환 코드에서 런타임에 ClassCastException이 발생할 수 있다. 하지만 이건 제네릭 타입 시스템의 취지에 어긋난다.
■ 실체화 불가 타입
E, List<E>, List<String> 같은 타입을 실체화 불가 타입이라 한다. 쉽게 말해, 실체화되지 않아서 런 타임에는 컴파일 타임보다 타입 정보를 적게 가지는 타입이다. 매개변수화 타입 가운데 실체화될 수 있는 타입은 List<?>와 Map<?,?> 같은 비한정적 와일드카드 타입뿐이다.
// 컬랙션 안의 원소 중 하나를 무작위로 선택해 반환
// 이 클래스를 사용하면 choose 메서드를 호출할 때마다 반환된 Object를 원하는 타입으로 형변환해야 한다
// 제네릭을 시급히 적용해야 한다
public class Chooser {
private final Object[] choiceArray;
public Chooser(Collection choices) {
choiceArray = choices.toArray();
}
public Object choose() {
Random rnd = ThreadLocalRandom.current();
return choiceArray[rnd.nextInt(choiceArray.length)];
}
}
// 리스트 기반 Chooser - 타입 안전성 확보! (168쪽)
// 코드 양이 조금 늘었지만, 런타임에 ClassCastException을 만날 일은 없으니 가치가 있음
public class Chooser<T> {
private final List<T> choiceList;
public Chooser(Collection<T> choices) {
choiceList = new ArrayList<>(choices);
}
public T choose() {
Random rnd = ThreadLocalRandom.current();
return choiceList.get(rnd.nextInt(choiceList.size()));
}
public static void main(String[] args) {
List<Integer> intList = List.of(1, 2, 3, 4, 5, 6);
Chooser<Integer> chooser = new Chooser<>(intList);
for (int i = 0; i < 10; i++) {
Number choice = chooser.choose();
System.out.println(choice);
}
}
}
* 참고할만한 포스팅 : 제네릭의 형변환
'Effective Java' 카테고리의 다른 글
[Effective Java] 아이템30 이왕이면 제네릭 메서드로 만들라 (0) | 2021.05.29 |
---|---|
[Effective Java] 아이템29 이왕이면 제네릭 타입으로 만들라 (0) | 2021.05.23 |
[Effective Java] 아이템27 비검사 경고를 제거하라 (0) | 2021.05.22 |
[Effective Java] 아이템26 로 타입은 사용하지 말라 (0) | 2021.03.08 |
[Effective Java] 아이템25 톱레밸 클래스는 한 파일에 하나만 담으라 (0) | 2021.03.02 |
최근댓글