예제 코드에서의 배열은 private 필드에 저장되고, 클라이언트에 반환되거나 다른 메소드에 전달되는 일이 전혀 없다. 또한 push 메소드를 통해 배열에 저장되는 원소의 타입은 항상 E이므로 이 비검사 형변환은 확실히 안전하다.
비검사 형변환이 안전함을 직접 증명했으므로 범위를 최소로 좁혀 @SuppressWarnings 를 사용해 해당 경고를 숨기면 된다.
// 배열 elements는 push(E)로 넘어온 E 인스턴스만 담는다.// 따라서 타입 안전성을 보장하지만,// 이 배열의 런타임 타입은 E[]가 아닌 Object[]다!@SuppressWarnings("unchecked")
publicStack() {
elements = (E[]) newObject[DEFAULT_INITIAL_CAPACITY];
}
E는 실체화 불가 타입이므로 컴파일러는 런타임에 이뤄지는 형변환이 안전한지 증명할 방법이 없다.
이번에도 마찬가지로 타입 안전성을 증명하고 경고를 숨길 수 있다.
// 비검사 경고를 적절히 숨긴다.publicEpop() {
if (size == 0)
thrownewEmptyStackException();
// push에서 E 타입만 허용하므로 이 형변환은 안전하다.@SuppressWarnings("unchecked")
Eresult = (E) elements[--size];
elements[size] = null; // 다 쓴 참조 해제returnresult;
}
첫 번째 방법과 두 번째 방법의 차이점
[배열을 사용하는 코드를 제네릭으로 만들려할 때의 해결책]
(1) Object 배열을 생성한 다음 제네릭 배열로 형변환한다.
(2) elements 필드의 타입을 E[] 에서 Object[] 로 바꾼다.
[차이점]
- (1)이 가독성이 더 좋다. 배열의 타입을 E[]로 선언하여 E 타입의 인스턴스만
받음을 확실히 어필한다. 또한 코드도 더 짧다.
- (1)에서는 형변환을 배열 생성 시 단 한번만 해주면 되지만, (2)에서는 배열에서
원소를 읽을 때마다 해줘야 한다.
- 따라서 현업에서는 (1)의 방식을 더 선호하며 자주 사용한다.
- 하지만 (E가 Object가 아닌 한) 배열의 런타임 타입이 컴파일타임 타입과 달라
힙 오염(heap pollution)을 일으킨다. 힙 오염이 마음에 걸리는 프로그래머는
두 번째 방식을 고수하기도 한다.
제네릭 타입으로 만든 스택을 사용해보자.
다음 코드는 Stack에서 꺼낸 원소에서 String의 toUpperCase를 호출할 때 명시적 형변환이 수행되지 않으며, 컴파일러에 의해 자동 생성된 형변환이 항상 성공함을 보장한다.
publicstaticvoidmain(String[] args) {
Stack<String> stack = newStack<>();
for (Stringarg : args)
stack.push(arg);
while (!stack.isEmpty())
System.out.println(stack.pop().toUpperCase());
}