배열은 제네릭 자료형과 두 가지 중요한 차이점을 갖고 있다. 

 첫 째는 배열은 공변 자료형이라는 것이다. 공변자료형이란 Sub가 Super의 하위자료형이랑 Sub[]도 Super[]의 하위자료형 이라는 것이다. 반면 제네릭은 불변 자료형이다. Type1과 Type2가 있을 때 List<Type1>과 List<Type2>는 서로 상위 자료형이나 하위자료형이 될 수 없다.

 먼저 위의 코드는 배열은 공변자료형이기 때문에 Long이 Object의 하위자료형이기 때문에 Long[]도 Object[]의 하위자료형이다. 따라서 컴파일단계에서 데이터를 저장하는데는 문제가 없지만, 실행단계에서 오류가 발생하게 된다. 아래의 코드는 제네릭은 불변자료형이라서 Long이 Object의 하위자료형 이더라도, List<Long>은 List<Object>의 하위자료형이 아니기때문에 컴파일단계에서 오류를 잡아내게 된다.

 두 번째 차이는 배열은 실체화되는 자료형이다. 즉 배열의 각 원소의 자료형은 실행할 때 결정된다는 것이다. 반면 제네릭은 컴파일 시점에만 자료형에 대한 조건들이 적용되고, 실제 실행할 때는 자료형에 대한 정보가 사라진다. 자료형 삭제덕에, 제네릭 자료형은 제네릭을 사용하지 않고 작성된 오래된 코드와도 문제없이 연동된다.


 이러한 기본적인 차이점 때문에 배열과 제네릭은 섞어 쓰기 어렵다. 예를 들어, 제네릭 자료형(List<E>)나 형인자 자료형(List<String>), 또는 형인자의 배열(E[])을 생성하는 것은 문법적으로 허용되지 않는다. 왜 허용되지 않을까? 형 안전성이 보장되지 않기 떄문이다. 문법이 허용된다면 컴파일러가 자동으로 만드는 형변환 구문(cast)들은 실행 도중에 ClassCastException 예외를 만들고 말았을 것이다.  


(1) 은 원래 오류구문이지만, 정상적으로 컴파일 된다고 가정하자. 
(3) 은 List<String>배열을 Object[]에 대입한다. 배열은 공변자료형이므로 가능하다. 
(4) 에서는 List<Integer>를 Object 배열에 있는 유일한 원소에 대입한다. List<String>[] 에다가 List<Integer>를 대입하는 형태인데, 제네릭이 실행 시 형 삭제가 되기 떄문에 List[] = List 와 같은 형태가 되기때문에 가능하다.
(5) stringLists안에 있는 데이터를 꺼내는 작업을 하고있는데, 컴파일러는 자동적으로 String으로 형변환을 하려고 할 것이다. 사실은 Integer데이터인데 !! 그렇기 때문에 ClassCastException이 발생하고 말것이다.

 이러한 문제를 방지하기 위해서 처음부터 문법적으로 제네릭배열을 막는것이다.


요약

제네릭과 배열이 따르는 자료형 규칙은 서로 다르다. 배열은 공변 자료형이자 실체화 가능 자료형이다. 제네릭은 불변 자료형이며, 실행 시간에 형인자의 정보는 삭제된다. 따라서 배열은 컴파일 시간에 형 안전성을 보장하지 못하며, 제네릭은 그 반대다. 일반적으로 배열과 제네릭은 쉽게 혼용할 수 없다. 만일 배열과 제네릭을 뒤섞어 쓰다가 컴파일 오류나 경고 메세지를 만나게 되면, 배열을 리스트로 바꿔야겠다는 생각이 들어야 한다.


+ Recent posts