Skip to content

Latest commit

 

History

History
164 lines (116 loc) · 3.68 KB

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

File metadata and controls

164 lines (116 loc) · 3.68 KB

아이템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 result;
    }

    // ...
}

런타임 예외 발생 가능성

public static void main(String[] args) {
    Stack stack = new Stack();
    stack.push("Hello");
    stack.push(123);
    String str = (String) stack.pop(); // ClassCastException 발생
    System.out.println(str);
}
  • 클라이언트가 직접 스택에서 꺼낸 객체를 형변환 해야함
    • ClassCastException이 발생할 위험성이 있음

제너릭 타입으로 변경하는 방법

클래스 선언에 타입 매개변수 추가

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;
    }

    // ...
}

에러 발생!

E와 같은 실체화 불가 타입으로는 배열을 만들 수 없다.

cf) E란?

  • 일반적으로 컬렉션 원소 타입의 매개변수 명에 사용되는 문자

해결책

1. Object 배열을 생성한 다음 제네릭 배열로 형변환

@SuppressWarnings("unchecked")
public Stack() {
    elements = (E[]) new Object[DEFAULT_INITIAL_CAPACITY];
}
  • 배열 elements는 private 필드에 저장되고 클라이언트, 다른 메서드에 전달되지 않음
  • push 메서드를 통해 배열에 저장되는 원소의 타입은 항상 E

→ 형변환이 확실히 안전함을 증명

2. elements 필드의 타입을 E[]에서 Object[] 로 변경 후 pop 할 때 형변환

public E pop() {
    if (size == 0)
        throw new EmptyStackException();

    @SuppressWarnins("unchecked")
    E result = (E) elements[--size];

    elements[size] = null;
    return result;
}
  • push에서 E 타입만 허용

→ 형변환이 확실히 안전함을 증명


1 vs 2

클래스 선언에 타입 매개변수 추가

  • 배열의 타입을 E[]로 선언하여 E 타입 인스턴스만 받음을 명확히 함
  • 형변환을 배열 생성 시 한번만 해줌
  • 배열의 런타임 타입과 컴파일타임 타입이 달라 힙 오염 일으킴

elements 필드의 타입을 E[]에서 Object[] 로 변경 후 pop 할 때 형변환

  • 배열에서 원소를 읽을 때 마다 형변환
  • 힙 오염을 일으키지 않음

cf) 힙 오염

주로 매개변수화 타입의 변수가 타입이 다른 객체를 참조할 때 발생한다.

List<Integer> list = new ArrayList<>();
list.add(1);

Object obj = list;

List<String> strList = (List<String>) obj;
  • 컴파일 단계에서는 오류가 발생하지 않음
  • 런타임 시 ClassCastException 발생

결론

  • 클라이언트가 직접 형변환 하는 타입보다 제네릭 타입을 사용해라
  • 새로운 타입을 설계할 때는 형변환 없이 사용할 수 있도록 하라