배열과 제네릭 타입의 차이
배열 - 공변 | 리스트 - 불공변
공변이란 함께 변한다는 뜻이다. 예를 들어 Sub가 Super의 하위 타입이라면 배열 Sub[]는 배열 Super[]의 하위 타입이 된다. 배열은 공변타입이다. 따라서 다음과 같은 코드를 작성해도 컴파일타임에 경고를 띄우지 않는다.
Object[] objectArray = new Long[1];
objectArray[0] = "타입이 달라 넣을 수 없다";
리스트는 불공변타입이다. 예를 들어 List<Type1>은 List<Type2>의 하위 타입도 아니고 상위 타입도 아니다. 따라서 컴파일타임에 에러를 확인할 수 있다.
List<Object> ol = new ArrayList<Long>();
ol.add("타입이 달라 넣을 수 없다.");
실체화(reify)의 여부
배열은 런타임에도 자신이 담기로 한 원소의 타입을 인지하고 확인한다. 그래서 Long배열에서 String을 넣으려 하면 예외가 발생한다.
하지만 제네릭 타입은 타입 정보가 런타임에는 소거된다. (이런 소거 메커니즘이 제네릭이 지원되기 전의 레거시 코드와 제네릭 타입을 함께 사용할 수 있도록 해주었다)
E, List List 같은 타입을 실체화 불가 타입이라고 한다. 실체화되지 않기 때문에 런타임에는 컴파일타임보다 타입 정보를 적게 가진다.이러한 차이로 제네릭과 배열은 조화롭지 못하며, 제네릭 배열을 만들지 못한다. 이를 허용하면 컴파일러가 자동으로 생성한 형변환 코드에서 런타임 예외가 발생할 수 있다.
제네릭 배열의 문제점
만약 T[]와 같은 제네릭 배열을 구현한다면, T라는 타입은 실행시간에선 정확한 자료형을 알 수 없으므로, Object[]로 변환된다. 이 배열을 Integer[] 레퍼런스에 참조시키게 되면 Object[]를 Integer[]로 런타임에 형변환 하지 못하므로 오류가 발생한다.
다음과 같이 리스트 배열을 만든다고 가정해보자. 제네릭 정보는 실행시간에 지워지므로 List[]가 된다. 즉, List는 들어오는 객체가 String형인지, Long형인지 모른다는 얘기이다. 즉, 실행시간에 String이 아닌 Integer를 집어넣는것이 가능해지고 이러면 컴파일 타임에 타입 체크를 하려는 제네릭의 의도를 벗어나기 때문에 지원하지 않는다.
List<String>[] list = new List<String>[10];