public class Stack {
private Object[] elements;
private int size = 0;
private static final int DEFAULT_INITIAL_CAPACITY = 16;
public Stack() {
elements = new Object[DEFAULT_INITIAL_CAPACITY];
}
public void push(Object e) {
ensureCapacity();
elements[size++] = e;
}
public Object pop() {
if (size == 0)
throw new EmptyStackException();
Object result = elements[--size];
elements[size] = null;
return result;
}
// ...
}
public static void main(String[] args) {
Stack stack = new Stack();
stack.push("Hello");
stack.push(123);
String str = (String) stack.pop(); // ClassCastException 발생
System.out.println(str);
}
- 클라이언트가 직접 스택에서 꺼낸 객체를 형변환 해야함
- ClassCastException이 발생할 위험성이 있음
public class Stack<E> {
private E[] elements;
private int size = 0;
private static final int DEFAULT_INITIAL_CAPACITY = 16;
public Stack() {
elements = new E[DEFAULT_INITIAL_CAPACITY]; //예외 발생
}
public void push(E e) {
ensureCapacity();
elements[size++] = e;
}
public E pop() {
if (size == 0)
throw new EmptyStackException();
E result = elements[--size];
elements[size] = null;
return result;
}
// ...
}
E와 같은 실체화 불가 타입으로는 배열을 만들 수 없다.
cf) E란?
- 일반적으로 컬렉션 원소 타입의 매개변수 명에 사용되는 문자
@SuppressWarnings("unchecked")
public Stack() {
elements = (E[]) new Object[DEFAULT_INITIAL_CAPACITY];
}
- 배열 elements는 private 필드에 저장되고 클라이언트, 다른 메서드에 전달되지 않음
- push 메서드를 통해 배열에 저장되는 원소의 타입은 항상 E
→ 형변환이 확실히 안전함을 증명
public E pop() {
if (size == 0)
throw new EmptyStackException();
@SuppressWarnins("unchecked")
E result = (E) elements[--size];
elements[size] = null;
return result;
}
- push에서 E 타입만 허용
→ 형변환이 확실히 안전함을 증명
- 배열의 타입을 E[]로 선언하여 E 타입 인스턴스만 받음을 명확히 함
- 형변환을 배열 생성 시 한번만 해줌
- 배열의 런타임 타입과 컴파일타임 타입이 달라 힙 오염 일으킴
- 배열에서 원소를 읽을 때 마다 형변환
- 힙 오염을 일으키지 않음
주로 매개변수화 타입의 변수가 타입이 다른 객체를 참조할 때 발생한다.
List<Integer> list = new ArrayList<>();
list.add(1);
Object obj = list;
List<String> strList = (List<String>) obj;
- 컴파일 단계에서는 오류가 발생하지 않음
- 런타임 시 ClassCastException 발생
- 클라이언트가 직접 형변환 하는 타입보다 제네릭 타입을 사용해라
- 새로운 타입을 설계할 때는 형변환 없이 사용할 수 있도록 하라