이번 장에서는 일반적인 배열을 사용한 클래스를 제네릭 타입으로 바꾸는 과정을 배우게 된다.
아래의 코드는 Object를 활용한 단순한 스택 클래스이다.
public class MyStack {
private Object[] elements;
private int size = 0;
private static final int DEFAULT_INITIAL_CAPACITY = 16;
public MyStack(){
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 boolean isEmpty(){
return size == 0;
}
private void ensureCapacity(){
if(elements.length == size)
elements = Arrays.copyOf(elements, 2 * size + 1);
}
}
이렇게 Stack을 구현하면 pop을 통해서 원소를 가져올 떄마다 형변환을 해줘야한다. 그러면 런타임 에러가 발생할 수 있기 때문에 제네릭으로의 변환이 시급하다.
제네릭 타입으로 바꾸는 첫 번째는 먼저 클래스 선언부에 타입 매개변수를 추가해준다.
public class MyStack<E> {
private E[] elements;
private int size = 0;
private static final int DEFAULT_INITIAL_CAPACITY = 16;
public MyStack(){
// 이 부분에서 컴파일이 안될 것이다. 그 이유는 컴파일 이후에는 제네릭 정보가 소거되기 때문에 컴파일 이후 생성자 호출시 어떤 타입으로 생성해야 할지 알 수 없기 때문이다.(실체화 불가)
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;
}
public boolean isEmpty(){
return size == 0;
}
private void ensureCapacity(){
if(elements.length == size)
elements = Arrays.copyOf(elements, 2 * size + 1);
}
중간에 주석 표시한 부분 때문에 컴파일이 되지 않을 것이다. 제네릭의 실체화 불가 떄문에 발생한다. 이를 해결하기 위한 해결책이 두 가지 존재한다.
public MyStack(){ elements = new E[DEFAULT_INITIAL_CAPACITY]; }
위의 코드가 컴파일이 안되기 때문에 우회를 해보자.
elements =(E[]) new Object[DEFAULT_INITIAL_CAPACITY];
이 처럼 바꾸게 되면 컴파일이 허락하게 되지만 경고를 보내게 된다. 바로 런타임 중에 타입의 안전성을 보장하지 못한다는 경고이다. 그러면 이 코드가 안전한지 직접 증명해보고 @SuppressWarnings 애너테이션을 사용해서 해당 경고를 숨겨야 한다.
그러면 타입이 안전할까? push 의 매개변수를 확인해보면 항상 들어오는 값이 E 타입이다. 그렇기 때문에 항상 비검사 형변환에 안전하게 된다. 그러면 이제 경고를 숨겨보자.
// 배열 elements는 push(E)로 넘어온 E 인스턴만 담는다.
// 따라서 타입 안전성을 보장하지만,
// 이 배열의 런타임 타입은 E[] 가 아닌 Object[] 다
@SuppressWarnings("unchecked")
public MyStack(){
elements = (E[]) new Object[DEFAULT_INITIAL_CAPACITY];
}
이 방법은 전체적으로 들어오는 코드부분만 수정하면 되기 때문에 많이 사용한다.
두 번째 방법은 elements를 E 타입이 아닌 Object 타입으로 바꾸는 것이다.
private Object[] elements;
이제 pop() 메서드에서 오류가 생기는데. 바로 밑에 부분에서 생기게 된다.
E result = elements[--size];
Object 값을 E 타입인 result에 참조값을 준다는 것인데. E는 실체화가 불가능한 타입이므로 Object가 안전한 형변환을 보장하지 못한다는 것이다.
첫 번째 방법과 동일하게 push를 통해서는 E 타입만을 받기 때문에 elements의 요소는 E 타입을 보장하게 된다. 그러면 이제 아래와 같이 경고를 없애고 형변환을 시켜주도록 하자.
@SuppressWarnings("unchecked") E result = (E)elements[--size];