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

심규환·2022년 3월 5일
0

Effective Java

목록 보기
27/29
post-thumbnail

이번 장에서는 일반적인 배열을 사용한 클래스를 제네릭 타입으로 바꾸는 과정을 배우게 된다.
아래의 코드는 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];

profile
장생농씬가?

0개의 댓글