본문 바로가기
Java

자바 Type-Erasure

by wwns 2023. 4. 30.
반응형

Type Erasure(타입 소거)

 

자바에서 제네릭 타입에 사용된 타입 정보를 컴파일 타임에만 사용하고 런타임에는 소거하여 런타임 오버헤드 발생을 줄이는 방식을 말한다

이게 무슨 말인지 잘 이해가 가지 않아 정리를 하면서 이해해 보는 시간을 가졌다.


Type Erasure 규칙

  • 모든 타입 파라미터를 그들의 바운드나 Object 타입으로 교체한다.
  • 제네릭 타입을 제거한 후 타입이 일치하지 않으면 타입 캐스팅을 추가한다
  • 확장된 제네릭 타입의 다형성을 보존하기 위해 브릿지 메서드를 생성한다.

각 규칙이 어떻게 적용되는 것인지 살펴보도록 한다

파라미터 타입을 바운디드와 언바운디드 타입으로 제네릭 클래스를 선언해 바운드 타입, Object 타입으로 컴파일 후 변환이 되는지 확인

Node 클래스 ByteCode

바이트 코드로 변환한 결과를 보면 Java/Lang/Object, Main1/Node로 언바운디드 타입으로 선언한 data는 Object로, Node라는 바운디드 타입은 Node로 교체되었다.

 

`T`의 언바운디드 타입을 바운디드 타입으로 교체하게 되면 Object가 아닌 바운디드 타입으로 교체된다

Comparable 이라는 bounded

 

이러한 과정은 제네릭 메서드에서도 똑같이 일어난다고 한다

만약 위 메서드를 count(Integer[] anArray, Integer elem) 같은 형태로 호출하게 되면 두 번째 규칙에 따라 Object를 Integer로 캐스팅한다고 생각하면 된다

 

제네릭 타입의 클래스나 인터페이스를 상속하거나 구현할 때 컴파일러는 필요에 따라 브릿지 메서드를 생성한다

public static class Node<T> {

    public T data;
    public Node<T> next;

    public Node(T data, Node<T> next) { 
    	this.data = data;
        this.next = next;
    }

    public void setData(T data) {
        this.data = data;
    }
}

public class MyNode extends Node<Integer> {
    public MyNode(Integer data, Node<Integer> next) { 
    	super(data, next);
    }

    @Override
    public void setData(Integer data) {
        super.setData(data);
    }
}

Node라는 제네릭 클래스에 MyNode가 Node를 상속받은 상황에서 타입소거를 진행하면 다음과 같이 변할 것이다.

public static class Node<T> {

    public Objcet data;
    public Node<Objcet> next;

    public Node(Objcet data, Node<Objcet> next) { 
    	this.data = data;
        this.next = next;
    }

    public void setData(Objcet data) {
        this.data = data;
    }
}

public class MyNode extends Node<Integer> {
    public MyNode(Integer data, Node<Integer> next) { 
    	super(data, next);
    }

    @Override
    public void setData(Integer data) {
        super.setData(data);
    }
}

MyNode에서 setData의 파라미터 타입이 일치하지 않기 때문에 메서드를 오버라이드 하지 못하게 되는 것이며

이러한 문제를 해결하고 다형성을 보존하기 위해 세 번째 규칙에 따라 브릿지 메서드를 생성한다.

bridge 메서드 생성

왜 이러한 작업들이 생겨났을까? 자바에서의 제네릭의 등장부터 생각해야 된다.
자바의 제네릭은 jdk 1.5부터 등장했고, 상위 버전과 하위 버전의 호환성 즉 하위 호환성을 맞추기 위해 제네릭이 도입되기 전의 코드도 정상적으로 작동시키기 위함
-> 제네릭을 지원하기 위해 런타임 시에 제네릭 정보를 지우는 Type erasure 방법이 도입된 것

위의 내용을 잘 알고 있다면 다음과 같은 상황에 대처할 방법을 고민해 볼 수 있을 것 같다.

제네릭을 파라미터로 받고, 타입에 따라 행하는 행위가 다를 때 같은 이름의 메서드로 정의를 해놓는 메서드 오버로딩을 생각하면서 비즈니스 로직을 구현했다면 아래와 같은 에러를 만날 것이다. 

Type Erasure로 인해 List<Integer> 와 List<String>은 런타임에 타입 정보를 알 수 없어 same erasure로 처리되는 것이다

같은 이름의 메서드가 두 개가 있다고 판단. 이러한 타입을 실체화 불가능 타입(non-reifiable)이라고 한다 

 

메서드의 이름을 바꾸어 사용하던가, 일급 컬렉션을 활용해 보는 등의 고민이 필요할 것으로 보인다

일급 컬렉션에 대해서도 정리를 해봐야겠다


Reference

https://docs.oracle.com/javase/tutorial/java/generics/erasure.html

https://www.baeldung.com/java-type-erasure

 

 

반응형