Skip to content

Latest commit

 

History

History
135 lines (121 loc) · 5.28 KB

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

File metadata and controls

135 lines (121 loc) · 5.28 KB

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

소주캉

제네릭 타입이 필요한 경우

  • 클라이언트에서 직접 형변환해야 하는 타입이 있다면 제네릭 타입으로 만들자.
  • 클라이언트가 형변환 없이 사용할 수 있다면 더 안전하고 쓰기 편해진다.

제네릭 타입이 필요한 예시

  • 임의의 타입을 받을 수 있게 Object의 배열로 구현한 stack예제
public class StackTest {
    private Object[] elements; // Object의 배열로 구현
    private int size = 0;
    private static final int DEFAULT_INITIAL_CAPACITY = 16;

    public StackTest() {
        this.elements = new Object[DEFAULT_INITIAL_CAPACITY];
    }
    
    public void push(Object e) {
        elements[size++] = e;
    }
    
    public Object pop() {
        if (size == 0) {
            throw new EmptyStackException();
        }
        Object result = elements[--size];
        elements[size] = null;
        return result;
    }
  • 코드에 오류는 발생하지 않는다. 하지만 클라이언트가 사용할 경우 명시적 형변환을 수행해주어야 한다.
  • 형변환이 적절하게 이뤄지지 않을 경우 Runtime오류가 발생한다.
public static void main(String[] args) {
        StackTest stack = new StackTest();
        stack.push(5);
        int element = (int)stack.pop(); // 클라이언트가 직접 형변환. 
    // 잠재적 런타임 오류의 위험이 있다. 
    }

제네릭 타입으로 바꾼 경우

  • 제네릭 타입으로 바꿀 경우 타입 오류를 Compile타임에 잡아낼 수 있다.
  • 클라이언트가 따로 형변환 해줄 필요가 없어 사용하기 쉽고 안전하다.
public class GenericStackTest<E> {
    private E[] elements;
    private int size = 0;
    private static final int DEFAULT_INITIAL_CAPACITY = 16;

    public GenericStackTest() {
        this.elements = new E[DEFAULT_INITIAL_CAPACITY]; // 실체화 불가 타입 에러
    }

    public void push(E e) {
        elements[size++] = e;
    }

    public E pop() {
        if (size == 0) {
            throw new EmptyStackException();
        }
        E result = elements[--size];
        elements[size] = null;
        return result;
    }
  • List, 제네릭 타입 변수실체화 불가 타입은 배열을 만들 수 없기 때문에 컴파일 오류가 발생한다.
실체화 불가 타입이란? 제네릭의 `타입 정보`가 런타임에는 소거(erasure)되어 원소 타입을 `컴파일타임`에만 검사하며 `런타임`에는 알 수 없다. 실체화 되지 않아서 런타임에는 컴파일 타임보다 타입 정보를 적게 가지는 타입이다.

배열을 사용하는 코드를 제네릭으로 만드는 방법

1. 제네릭 배열 생성 금지 우회

  • 배열은 Object[]로 생성하고 이를 타입변수 E[]로 형변환 한다. 런타임에서 지정한 타입의 배열로 바뀐다(ex: int[])
  • 컴파일 오류 대신 경고를 내보낸다.
  • 비검사 형변환이 타입 안전한지 개발자가 스스로 증명해야한다.
  • 안전함을 확신했다면 경고를 숨긴다.
@SuppressWarnings("unchecked")
public GenericStackTest() {
    this.elements = (E[]) new Object[DEFAULT_INITIAL_CAPACITY];
    }
  • 첫 번째 방법이 가독성이 더 좋고 코드가 더 짧아 현업에서 선호된다.
  • 그러나 배열의 런타임 타입: E[]컴파일 타입 Object[]과 달라 힙 오염을 일으킨다.
힙 오염이란? 실체화 불가 타입은 런타임에서 타입 정보가 소멸되므로 제네릭 배열의 런타임 타입이 컴파일 타임의 타입과 다르게 된다. 이처럼 매개변수화 타입의 변수가 타입이 다른 객체를 참조하는 것을 힙 오염이라 한다.

2. 배열 타입을 Object[]로 변경

  • elements 필드의 타입을 E[]에서 Object[]로 변경
  • 힙오염에서 안전하다.
  • 첫 번째 방식에서는 형변환을 배열 생성시 단 한번만 한다.
  • 두 번째 방식은 배열에서 원소를 읽을 때마다 형변환 해줘야 한다.
public class GenericStackTest<E> {
    private Object[] elements;
    private int size = 0;
    private static final int DEFAULT_INITIAL_CAPACITY = 16;

    public GenericStackTest() {
        this.elements = new Object[DEFAULT_INITIAL_CAPACITY];
    }

    public void push(E e) {
        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 static void main(String[] args) {
        GenericStackTest<Integer> stack = new GenericStackTest<>();
        stack.push(5);
        int element = stack.pop(); // (int)형변환 필요 없음
    }