감이 많이 떨어진 나를 발견하는 요즘이다. 복습, 집중.
ArrayList 구현을 위해, 자연스레 제네릭을 이용한 배열을 내부적으로 구성한다. 제네릭은 컴파일타임까지만 정보를 유지하고 타입소거로 인해 런타임에서 Object타입으로 변경된다. 따라서, 인스턴스 변수로 제네릭 배열을 구성했다.
public class MyArrayList<E> implements MyList<E> {
private E[] arr;
private int size;
private E head;
private E tail;
}
ArrayList가 사용하는 배열 공간의 크기를 특정하기 위해, Capacity를 초기화하기 위해 다음과 같이 생성자를 구성했다.
public class MyArrayList<E> implements MyList<E> {
private E[] arr;
private int size;
private E head;
private E tail;
public MyArrayList(final Integer arrayCapacity) {
this.arr = new E[arrayCapacity];
}
}
문제점을 찾았는가?
arrayCapacity argument에 대한 방어로직이 세워져 있지 않다는 점을 지적할 수 있겠지만, 이 글에서 말하고자 하는 내용은 그게 아니다.
문제는 new E[arrayCapacity]; 에서 발생한다.
new E(); 와 같은 구체 인스턴스 생성은 불가 하더라도, 배열은 생성됨직 하지 않는가?
public MyArrayList(final Integer arrayCapacity) {
this.arr = new E[arrayCapacity];
}
위와 같은 경우, E가 Object로 변환될 것을 예측할 수 있다. 이에따라, 실제 인스턴스가 Object[]로 생성되게 되며 컴파일러가 캐스팅 코드를 삽입할 것이다.
배열은 공변성을 가짐으로, 실제 인스턴스의 타입 계층구조에 따라 캐스팅이 가능하다. 다만, 이 경우, 실제 인스턴스가 Object[] 타입으로 인스턴스화 되게 됨으로 후에 제네릭 타입에 따라 컴파일러가 캐스팅 코드를 삽입하여 실행하게 된다, 이로인해 ClassCastingException가 발생하게 된다.
정리하자면, new T[] 는 new Object[]로 변환이 맞으나, 실제 인스턴스의 타입이 Object[]가 됨으로 후에 다운 캐스팅 시 ClassCastException 이 발생하게 된다.
제네릭이 개발자의 런타임 에러를 미리 감지하고 예방하기 위해 등장했다는 점을 기억하자