이펙티브 자바 - item 29. 이왕이면 제네릭 타입으로 만들라

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

이펙티브 자바 - item 29. 이왕이면 제네릭 타입으로 만들라

단순 스택 코드를 제너릭으로 변경해보자.

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 elements[--size];
    }

    public boolean isEmpty() {
        return size == 0;
    }

    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();
        }
        E result = elements[--size];
        elements[size] = null;
        return elements[--size];
    }

    public boolean isEmpty() {
        return size == 0;
    }

    private void ensureCapacity() {
        if (elements.length == size) {
            elements = Arrays.copyOf(elements, 2 * size + 1);
        }
    }
}

이렇게 타입 매개변수를 추가하면 오류가 발생한다. E와 같은 실체화 불가 타입으로 배열을 만들 수 없어 생기는 오류이다.

이는 배열을 사용하는 코드를 제네릭으로 만들려 할 때 마다 마주쳐야하는 에러이다.

이를 해결하기 위해서는 두 가지 해결책이 존재한다.

  1. 제너릭 배열 생성을 금지하는 제약을 대놓고 우회하는 방법
public class Stack<E> {
    private E[] elements;
    private int size = 0;
    private static final int DEFAULT_INITIAL_CAPACITY = 16;

    @SuppressWarnings("unchecked")
    public Stack() {
        elements = (E[]) new Object[DEFAULT_INITIAL_CAPACITY]; // Object 배열을 선언한 다음 제너릭 배열로 형변환
    }

    ...
}

위의 코드와 같이 Object 배열을 생성한 다음 제너릭 배열로 형변환하면 컴파일러가 오류 대신 경고를 내보내는 것을 확인할 수 있다. 하지만 이는 일반적으로 타입 안전하지 않다.

따라서 이 비검사 형변환이 프로그램의 타입 안정성을 해치지 않음을 개발자가 직접 확인하고 @SuppressWarnings 애너테이션으로 해당 경고를 숨겨야 한다.

  1. elements 필드의 타입을 E[]에서 Object[]로 수정한다.
public class Stack<E> {
    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(E e) {
        ensureCapacity();
        elements[size++] = e;
    }

    public E pop() {
        if (size == 0) {
            throw new EmptyStackException();
        }
				// 경고가 뜨는 부분
        @SuppressWarnings("unchecked") E result = (E) 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);
        }
    }
}

이번에는 pop에서 경고가 뜨게 되는데 이 부분을 @SuppressWarnings("unchecked") 처리하여 경고를 숨겨주자

첫번 째 방법이 형변환을 배열 생성 시에 한번만 해주고 가독이 더 좋아 개발자들이 더 선호하며 자주 자주 사용하지만, 배열의 런타임 타입이 컴파일 타입과 달라 힙 오염을 일으킨다.

한정적 타입 매개변수를 사용한 제네릭

class DelayQueue<E extencds Delayed> implements BlockingQueue<E>

타입 매개변수에 제약을 두는 제네릭 타입도 있다. 위의 코드는 Delayed의 하위 타입만 매개변수로 받는다는 의미이다. 클라이언트는 DelayQueue에서 형변환 없이 곧바로 Delayed의 메서드를 호출 할 수 있다.

모든 타입은 자기 자신의 하위 타입이므로 DelayQueue로도 사용할 수 있다.

정리

클라이언트가 직접 형변환을 해야하는 타입보다 제네릭 타입이 더 안전하고 쓰기 편하다.

그러니 새로운 타입을 설계할 때는 형변환 없이도 사용 할 수 있게 하라.

가장 좋은 방법은 제네릭 타입을 사용하는 방법이다.