배열과 제네릭 타입에는 중요한 두가지 차이가 있다.
1. 배열은 공변(covariant)이지만, 제너릭은 불공변이라는 점
2. 배열은 실체화되지만, 제너릭은 실체화가 불가능하다.
Sub
가 Super
의 하위 타입이라면 배열 Sub[]
은 Super[]
타입의 하위 타입이 되는 이런 성질을 공변이라고 한다.
String
가Object
의 서브타입이면 String[]
은 Object[]
의 서브타입이다.String
가Object
의 서브타입이지만, List<String>
은 List<Object>
의 서브타입이 아니다.그래서 아래와 같이 공변성이 만족이 되면
Object[] objectArray = new Long[1];
objectArray[0] = "asdf"; //ArrayStoreException을 던짐
이런 코드는 문법상 허용되지만, Long 형 배열에 string을 넣은 실수가 runtime에서야 오류가 발견된다.
아래와 같이 공변성이 충족이 되지않으면 애초에 컴파일이 되지 않는다.
List<Object> ol = new ArrayList<Long>(); //호환되지 않은 타입
ol.add("asdf");
Object[] objectArray = new Long[1];
배열은 런타임에 타입이 실체화되기 때문에 Object[]
배열은 런타임에 Long[]
이 된다.
List<String> ol = new ArrayList<String>();
그에비해 제너릭 타입은 런타임에는 그냥 타입이 소거된 ArrayList 만 타입으로 남게 된다. 원소타입은 위에서 봤듯이 컴파일 타임에서만 검사한다.
이러한 소거는 제네릭이 지원되기 전에 레거시 코드와 제네릭 타입을 함께 사용할 수 있게 해주는 메커니즘으로, 자바5가 제네릭으로 순조롭게 전환될 수 있도록 해줬다.
타입 안전하지 않기 때문이다.
이를 허용한다면 컴파일러가 자동 생성한 형변환 코드에서 런타임에 ClassCastException이 발생할 수 있다.
List<String>[] stringLists = new List<String>[]; //(1)
List<Integer> intList = List.of(42); //(2)
Object[] objects = stringLists; //(3)
objects[0] = intList; //(4)
String s = stringLists[0].get(0); //(5)
그래서 고로 제네릭 배열이 생성되지 않도록 (1)에서 컴파일 오류를 내야한다.
배열을 제너릭으로 만들 수 없어 귀찮을 때도 있다.
1. 제네릭 컬랙션 -> 해당 원소타입의 배열로 반환은 보통 불가능
2. 제네릭 타입과 가변인수 메서드를 함께 쓰면 해석하기 어려운 경고메세지를 받음
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)];
}
}
위의 클래스를 사용하려면 choose
메서드를 호출할 때마다 반환된 Object를 원하는 타입으로 형변환해줘야 한다. 혹시나 타입이 다른 원소가 들어있었다면 런타임에 형변환 오류가 날 것이다.
이 클래스를 제네릭으로 만들어보자.
public class Chooser<T> {
private final T[] choiceArray;
public Chooser(Collection<T> choices) {
choiceArray = choices.toArray();
}
}
작성하였지만, 컴파일 되지 않는다. choices.toArray()
여기에서 Object[]
가 T[]
로 변환될 수 없다(incompatible type) 라고한다.
그래서 Object 배열을 T형 배열로 간단하게 형변환 시켜준다.
choiceArray = (T[]) choices.toArray();
그랬더니 이번엔 경고가 뜬다.
Chooser.java:9: warning: [unchecked] unchecked cast
결국에 비검사 형변환 경고를 제거하려면 배열대신 리스트를 쓰면 된다.
public class Chooser<T> {
private final List<T> choiceArray;
public Chooser(Collection<T> choices) {
choiceArray = new ArrayList<>(choices);
}
public Object choose() {
Random rnd = ThreadLocalRandom.current();
return choiceArray[rnd.nextInt(choiceArray.length)];
}
}
배열과 제네릭은 공변성과 실체화에 있어서 매우 큰 차이를 보인다. 그 결과 배열은 런타임에 타입 안전하지 않지만, 제네릭(리스트)는 컴파일 타임에 검사가 다 되므로 리스트를 되도록 사용하자.