목표

  • 타입 소거의 의미를 이해한다.
  • 타입 소거가 필요한 이유를 이해한다.

타입 소거의 의미

  • 타입 파라미터에 대해서 컴파일 타임에만 타입 검사를 하고 런타임에는 타입에 대한 정보를 삭제하는 방식
  • 아래와 같은 코드가 있다고 가정하면
public static  <E> boolean containsElement(E [] elements, E element){
    for (E e : elements){
        if(e.equals(element)){
            return true;
        }
    }
    return false;
}
  • 런타임에는 아래와 같이 동작한다.
    • unbound 타입인 EObject 타입으로 치환되었다.
public static  boolean containsElement(Object [] elements, Object element){
    for (Object e : elements){
        if(e.equals(element)){
            return true;
        }
    }
    return false;
}

타입 소거 종류

타입 변환

  • 타입 파라미터가 unbound 상태일 때는 Object, bound 상태일 때는 첫 번째 bound로 변환한다.
// 변환 전
public class Stack<E> { 
	private E[] stackContent;
}
// 변환 후
public class Stack { 
	private Object[] stackContent;
}
// 변환 전
public class BoundStack<E extends Comparable<E>> { 
	private E[] stackContent;
}
// 변환 후
public class BoundStack {
    private Comparable [] stackContent;
}

브릿지 메소드 생성

  • 다형성을 유지하기 위해서 필요한 기능이다.
  • 아래 예제에서 Stack은 타입 소거로 인해 push의 인자가 Object가 되어버린다.
public class IntegerStack extends Stack<Integer> {

    public IntegerStack(int capacity) {
        super(capacity);
    }

    public void push(Integer value) {
        super.push(value);
    }
}
  • 컴파일 타임에 브릿지 메소드가 생성되어 이를 해결해준다.
public class IntegerStack extends Stack {
    
    // Bridge method generated by the compiler
    public void push(Object value) {
        push((Integer)value);
    }

    public void push(Integer value) {
        super.push(value);
    }
}

타입 소거의 목적

  • 제네릭이 존재하지 않던 버전과 호환성을 보장하기 위해서다.
  • 런타임에 타입 파라미터에대한 오버헤드가 발생하지 않는다.

참고 자료