java: generic array creation 오류 (공변, 불공변, 실체화)

김태훈·2024년 1월 5일
0
post-thumbnail

다음 코드를 보자.

ArrayList<String>[] stringList = new ArrayList<String>[3];

제네릭 배열을 선언하였을 때, 문제가 되는 부분이 있는가? 이를 알아보자.

1. 제네릭과 배열의 차이점

(1) 공변(Covariant)과 불공변(Invariant)

제네릭을 배울 때, 공변에 관한 개념이 등장했다. 자바의 정석에서는 간단하게 언급만하고 넘어가서 중요치 않은 개념이라고 생각했지만, 꽤나 비중있는 개념이었다.

잘 설명한 글이다.
https://stackoverflow.com/questions/8481301/covariance-invariance-and-contravariance-explained-in-plain-english

공변(Covariant)

Sub이라는 클래스가 Super 클래스의 하위 클래스일 때, Sub클래스의 유형이 변환되었을때 (예를들어 List<Sub>), Super클래스가 같은 유형으로 변환되면, 그 결과 마저도 Sub유형이 Super유형의 하위 타입이어야 한다는 점이다.
이 때, 그러한 유형 변환을 공변하다고 한다.

불공변(Invariant)

불공변은 Sub클래스가 Super클래스의 하위 클래스일 때, 유형이 변환되었을 때, 어떠한 두 클래스 누구도 상대방의 하위 타입일 수 없는 유형 변환이다.

그렇다면? 예시로 이해하자

String 클래스는 Object 클래스의 하위 타입이다. 이 때, 만일 해당 클래스를 지네릭스화하여 List의 원소 타입으로 추가한다고 생각하자.
그렇다면 List<String>, List<Object> 가 될 것이다.
하지만 두 List는 불공변하다. 지네릭스가 그렇다. 그 이유는 깊게 생각하지 말고 일단은 지네릭스는 불공변하다는 사실을 기억하자.

다만 배열의 경우는 다르다.
String[]Object[] 배열은 String 배열이 Object배열의 하위 타입이다. 따라서 배열은 공변하다.

(2) 따라서

ArrayList<String> strings = new ArrayList<Object>();
ArrayList<Object> objects = new ArrayList<String>();

위의 코드는 컴파일 되지 않는다.
지네릭스는 불공변하기 때문이다.

(3) 배열과 지네릭스의 런타임

배열의 런타임

배열은 실제로 런타임에 타입이 구체화 된다.

Object[] arr = new String[3];

이렇게 코드를 작성하면 arr배열은 String타입의 원소만 포함할 수 있다.

지네릭스의 런타임

지네릭스는 타입의 안전성에 기여한다. 따라서 런타임에 타입이 잘못 정의되었는지 체크한다. 즉, 지네릭스는 런타임에 타입 체크한 후 할일을 다할 뿐이다.

List<Object> list = new ArrayList<String>();

이렇게 작성하면 당연히 컴파일 에러가 발생한다.
지네릭스는 런타임에 타입체크 후 사라지고, 남는 것은 List list = new ArrayList(); 일 뿐이다.

2. 지네릭 배열의 컴파일 오류

그렇다면 서두에 던졌던 코드가 어째서 컴파일 에러를 발생하는 것일까?

ArrayList<String>[] stringList = new ArrayList<String>[3];

지네릭 자체로는 문제가 없다.
지네릭이 제거가 되면
ArrayList[] stringList = new ArrayList[3] 되는데
이렇게 되면,
List<Integer> integerList = Arrays.asList(3);
을 stringList에 추가할 수 있다.
타입의 안전성이 완전 깨져버린다는 뜻이다.

정리하자면, array는 반드시 runtime에 원소의 타입을 알 수 있어야 한다. 하지만 지네릭스를 원소로 가지는 배열은 compile time에 타입 체크를 하고, runtime에 지네릭 타입을 모두 날려버리므로, 컴파일러는 generic 배열을 직접적으로 생성하지 못하도록 한다.

3. 지네릭 배열을 사용하고 싶다면?

<?> 를 이용하자

ArrayList<?> [] stringList = new ArrayList<?>[3];

아니면 이렇게도 가능하다.

List<Integer>[] list = new ArrayList[n+1];

많은 도움 받았습니다.
포스트1
포스트2
포스트3
baeldung

profile
기록하고, 공유합시다

0개의 댓글