다음 코드를 보자.
ArrayList<String>[] stringList = new ArrayList<String>[3];
제네릭 배열을 선언하였을 때, 문제가 되는 부분이 있는가? 이를 알아보자.
제네릭을 배울 때, 공변에 관한 개념이 등장했다. 자바의 정석에서는 간단하게 언급만하고 넘어가서 중요치 않은 개념이라고 생각했지만, 꽤나 비중있는 개념이었다.
Sub이라는 클래스가 Super 클래스의 하위 클래스일 때, Sub클래스의 유형이 변환되었을때 (예를들어 List<Sub>
), Super클래스가 같은 유형으로 변환되면, 그 결과 마저도 Sub유형이 Super유형의 하위 타입이어야 한다는 점이다. 이 때, 그러한 유형 변환을 공변하다고 한다.
불공변은 Sub클래스가 Super클래스의 하위 클래스일 때, 유형이 변환되었을 때, 어떠한 두 클래스 누구도 상대방의 하위 타입일 수 없는 유형 변환이다.
String 클래스는 Object 클래스의 하위 타입이다. 이 때, 만일 해당 클래스를 지네릭스화하여 List의 원소 타입으로 추가한다고 생각하자. 그렇다면 List<String>
, List<Object>
가 될 것이다. 하지만 두 List는 불공변하다. 지네릭스가 그렇다. 그 이유는 깊게 생각하지 말고 일단은 지네릭스는 불공변하다는 사실을 기억하자.
다만 배열의 경우는 다르다. String[]
과 Object[]
배열은 String 배열이 Object배열의 하위 타입이다. 따라서 배열은 공변하다.
ArrayList<String> strings = new ArrayList<Object>();
ArrayList<Object> objects = new ArrayList<String>();
위의 코드는 컴파일 되지 않는다. 지네릭스는 불공변하기 때문이다.
배열은 실제로 런타임에 타입이 구체화 된다. 이를 실체화라고 한다.
Object[] arr = new String[3];
이렇게 코드를 작성하면 arr배열은 String타입의 원소만 포함할 수 있다.
지네릭스는 타입의 안전성에 기여한다. 따라서 런타임에 타입이 잘못 정의되었는지 체크한다. 즉, 지네릭스는 런타임에 타입 체크한 후 할일을 다할 뿐이다.
List<Object> list = new ArrayList<String>();
이렇게 작성하면 당연히 컴파일 에러가 발생한다. 지네릭스는 런타임에 타입체크 후 사라지고, 남는 것은 List list = new ArrayList();
일 뿐이다.
그렇다면 서두에 던졌던 코드가 어째서 컴파일 에러를 발생하는 것일까?
ArrayList<String>[] stringList = new ArrayList<String>[3];
지네릭 자체로는 문제가 없다. 지네릭이 제거가 되면 ArrayList[] stringList = new ArrayList[3]
되는데 이렇게 되면, List<Integer> integerList = Arrays.asList(3);
을 stringList에 추가할 수 있다. 타입의 안전성이 완전 깨져버린다는 뜻이다.
<?>
를 이용하자
ArrayList<?> [] stringList = new ArrayList<?>[3];
많은 도움 받았습니다. -> 블로그 포스트