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

신명철·2022년 2월 23일
0

Effective Java

목록 보기
27/80
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 boolean isEmpty() {
        return size == 0;
    }
    
    private void ensureCapacity() {
        if (elements.length == size)
            elements = Arrays.copyOf(elements, 2 * size + 1);
    }
}
  • 만약 클라이언트가 위 Stack 클래스를 사용하려면 Stack 에서 꺼낸 객체를 형변환해서 사용을 해야한다. 이러면 런타임 오류가 날 위험이 있다.
  • 일반 클래스를 제네릭 클래스로 만드는 첫 단계는 클래스 선언에 타입 매개변수를 추가하는 일이다.
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;
    }
    
    public boolean isEmpty() {
        return size == 0;
    }
    
    private void ensureCapacity() {
        if (elements.length == size)
            elements = Arrays.copyOf(elements, 2 * size + 1);
    }
}
  • 타입 이름으로 보통 E를 사용한다. 그런 후 Object를 적절한 타입 매개 변수로 바꾸고 컴파일 하면 하나 이상의 오류가 발생하는데, 그 이유는 E는 실체화 불가 타입이기 때문이다.
  • 배열을 사용하는 코드를 제네릭으로 만들 때 항상 발생하는 문제다.

해결책

1. 제네릭 배열 생성을 금지하는 제약을 대놓고 우회하는 방법

    public Stack() {
        elements = (E[]) new Object[DEFAULT_INITIAL_CAPACITY];
    }
  • Object 배열을 생성하고 이를 제네릭 배열로 형변환했다.
  • 하지만 이런 방식은 타입 안전하지가 않다.
  • 해당 방식을 사용하기 위해서는 타입 안전한지를 직접 증명한 후 범위를 최소로 좁혀 @SuppressWarnings("unchecked") 어노테이션을 사용해 경고를 숨겨서 사용할 수 있다.
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];
    }
    
    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);
    }
}
  • 이러한 방식은 가속성이 좋도 코드도 짧다.

2. 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);
    }
}
  • E가 실체화 불가 타입이기 때문에 런타임에 이뤄지는 형변환이 안전한지 증명할 방법이 없다
  • 그렇기 때문에 직접 증명하고 경고를 숨겨야 한다.
@SuppressWarnings("unchecked")
        E result = (E) elements[--size];
  • 배열의 원소를 읽을 때마다 형변환을 해줘야 한다.

Stack 처럼 대다수의 제네릭 타입은 타입 매개변수에 아무런 제약을 두지 않는다. Stack<Object>, Stack<int[]>, Stack<List<String>>, Stack 등 어떤 참조타입으로도 Stack을 만들 수 있다.

타입 매개변수에 제약을 두는 제네릭 타입도 있다.

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

타입 매개변수 목록인 <E extends Delayed>java.util.concurrent.Delayed의 하위타입만 받는다는 뜻이다. 이러한 타입 매개변수를 한정적 타입 매개변수라고 한다. 모든 타입은 자기 자신의 하위 타입이기 때문에 DelayQueue<Delayed\>로도 사용할 수 있음을 기억하자

profile
내 머릿속 지우개

0개의 댓글