JDK가 제공하는 제네릭 타입과 메서드를 사용하는 일은 일반적으로 쉬운 편이지만, 제네릭 타입을 새로 만드는 일은 조금 더 어렵다. 아이템7에서 다룬 단순한 스택 코드를 살펴보자
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();
return elements[--size];
}
/**
* 원소를 위한 공간을 적어도 하나 이상 확보한다.
* 배열 크기를 늘려야 할 때마다 대략 두 배씩 늘린다.
*/
private void ensureCapacity() {
if(elements.length == size)
elements = Arrays.copyOf(elements, 2 * size + 1);
}
}
위의 클래스는 원래 제네릭 타입이어야 마땅하다. 그러니 제네릭으로 만들어 보자, 제네릭으로 가는 첫 단계는 클래스 선언에 타입 매개변수를 추가하는 일이다.
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();
return elements[--size];
}
// 나머지 메서드는 그대로다
}
이 단계에서는 하나 이상의 오류가 뜨는데 이 클래스도 예외는 아니다.
elements = new E[DEFAULT_INITIAL_CAPACITY];
앞장에서 설명한 것처럼, E와 같은 실체화 불가 타입으로는 배열을 만들 수 없다. 배열을 사용하는 코드를 제네릭으로 만들려 할 때는 이 문제가 항상 발목을 잡을 것이다.
해결책으로는 제네릭 배열 생성을 금지하는 제약을 대놓고 우회하는 방법이다. Object 배열을 생성한 다음 제네릭 배열로 형변환해보자. 이제 컴파일러는 오류 대신 경고를 내보낼 것이다.
elements = (E[]) new Object[DEFAULT_INITIAL_CAPACITY];
컴파일러는 이 프로그램이 타입 안전한지 증명할 방법이 없지만, 우리는 할 수 있다. 따라서 이 비검사 형변환이 프로그램의 타입 안정성을 해치지 않음을 우리 스스로 확인해야 한다. 문제의 배열 elements는 private 필드에 저장되고, 클라이언트로 반환되거나 다른 메서드에 전달되는 일이 전혀 없다. 이 비검사 형변환은 안전하다.
안전함이 증명됬으니 이제 @SuppressWarnings 애너테이션으로 해당 경고를 숨기자
// 배열 elements는 push(E)로 넘어온 E 인스턴스만 담는다.
// 따라서 타입 안정성을 보장하지만,
// 이 배열의 런타임 타입은 E[]가 아닌 Object[]다!
@SuppressWarnings("unchecked")
public Stack() {
elements = (E[]) new Object[DEFAULT_INITIAL_CAPACITY];
}
이렇게 함으로써 Object기반 Stack을 제네릭으로 바꿀 수 있었다. 이 예시처럼 대다수의 제네릭 타입은 타입 매개변수에 아무런 제약을 두지 않고 만들 수 있다Stack<Object>, Stack<int[]>, Stack<List<String>>
단, 기본 타입은 예외다.
클라이언트에서 직접 형변환해야 하는 타입보다 제네릭 타입이 더 안전하고 쓰기 편하다. 그러니 새로운 타입을 설계할 때는 형변환 없이도 사용할 수 있도록 하라. 그렇게 하려면 제네릭 타입으로 만들어야 할 경우가 많다.