Effective Java 29. 이왕이면 제네릭 타입으로 만들라

Jung Ho Seo·2020년 7월 30일
0

EffectiveJava

목록 보기
10/35
post-thumbnail

Generic

JDK가 제공하는 제네릭 타입과 메서드를 사용하는 일은 일반적으로 쉬운 편이지만, 제네릭 타입을 새로 만드는 일은 조금 더 어렵다. 아이템7에서 다룬 단순한 스택 코드를 살펴보자

Object 기반 스택 - generic이 절실하다

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 애너테이션으로 해당 경고를 숨기자

@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>> 단, 기본 타입은 예외다.

클라이언트에서 직접 형변환해야 하는 타입보다 제네릭 타입이 더 안전하고 쓰기 편하다. 그러니 새로운 타입을 설계할 때는 형변환 없이도 사용할 수 있도록 하라. 그렇게 하려면 제네릭 타입으로 만들어야 할 경우가 많다.

profile
책, 글, 개발

0개의 댓글