[JAVA]Generic-Type Erasure

doxxx·2022년 2월 23일
1

자바 고급 스터디

목록 보기
3/3
post-thumbnail

Type Erasure

컴파일 타입에만 타입 제약 조건을 정의하고, 런타임에는 타입을 제거한다.
이를통하여 매개변수화 된 타입에 대해 새로운 클래스가 생성되지 않게 하여 런타입 오버헤드 발생을 막는다.

제네릭 타입 제거

타입 제거프로세스에서 자바 컴파일러는 타입 매개변수를 모두 지우고,
1. 제한이 없을 경우 Object로 대체한다.
2. 제한이 있을 경우 첫 경계로 대체한다.

1의 예시로 단일 링크드 리스트인 Node클래스 예시를 보자

public class Node<T> {

    private T data;
    private Node<T> next;

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

    public T getData() { return data; }
    // ...
}

타입 매개변수 T는 제한이 없기 때문에 자바 컴파일러는 이를 Object로 대체한다.

public class Node {

    private Object data;
    private Node next;

    public Node(Object data, Node next) {
        this.data = data;
        this.next = next;
    }

    public Object getData() { return data; }
    // ...
}

2의 예시로 제한된 타입 매개변수가 있는 Node클래스 예시를 보자

public class Node<T extends Comparable<T>> {

    private T data;
    private Node<T> next;

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

    public T getData() { return data; }
    // ...
}

자바 컴파일러는 타입 매개변수 T를 첫 경계 클래스인 Comparable로 대체한다.

public class Node {

    private Comparable data;
    private Node next;

    public Node(Comparable data, Node next) {
        this.data = data;
        this.next = next;
    }

    public Comparable getData() { return data; }
    // ...
}

제네릭 메서드에서의 타입 제거

자바 컴파일러는 제네릭 메서드의 타입 매개변수 또한 제거한다.
다음은 배열의 인수 발생의 횟수를 카운트하는 메서드이다.
제한이 없는 타입 매개변수의 경우

public static <T> int count(T[] anArray, T elem) {
    int cnt = 0;
    for (T e : anArray)
        if (e.equals(elem))
            ++cnt;
        return cnt;
}

자바 컴파일러는 제한이 없는 타입 매개변수 T를 Object로 대체한다.

public static int count(Object[] anArray, Object elem) {
    int cnt = 0;
    for (Object e : anArray)
        if (e.equals(elem))
            ++cnt;
        return cnt;
}
class Shape { /* ... */ }
class Circle extends Shape { /* ... */ }
class Rectangle extends Shape { /* ... */ }

public static <T extends Shape> void draw(T shape) { /* ... */ }

다른 모양을 그리는 제네릭 메서드는 위와 같이 만들 수 있고, 자바 컴파일러는 T를 Shape로 대체한다.

public static void draw(Shape shape) { /* ... */ }

타입 제거의 영향과 브릿지 메서드

타입 제거로 인해서 타입 안정성에 영향이 생기게 된다.

NodeMyNode의 예시를 살펴보자

public class Node<T> {

    public T data;

    public Node(T data) { this.data = data; }

    public void setData(T data) {
        System.out.println("Node.setData");
        this.data = data;
    }
}

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

    public void setData(Integer data) {
        System.out.println("MyNode.setData");
        super.setData(data);
    }
}

컴파일러의 타입 제거후의 코드는 다음과 같다.

public class Node {

    public Object data;

    public Node(Object data) { this.data = data; }

    public void setData(Object data) {
        System.out.println("Node.setData");
        this.data = data;
    }
}

public class MyNode extends Node {

    public MyNode(Integer data) { super(data); }

    public void setData(Integer data) {
        System.out.println("MyNode.setData");
        super.setData(data);
    }
}

Node.setData(T) 메서드가 Node.setData(Object)가 되었다.
메서드 시그니처(선언부의 메서드 이름, 매개변수 목록)가 서로 일치하지 않게 되었다.
MyNode.setData(Integer)메서드는 Node.setData(Object)를 오버라이딩 하지 못한다.

따라서 자바 컴파일러는 다형성을 보존하기위해 브릿지 메서드를 생성한다.

class MyNode extends Node {

    // Bridge method generated by the compiler
    //
    public void setData(Object data) {
        setData((Integer) data);
    }

    public void setData(Integer data) {
        System.out.println("MyNode.setData");
        super.setData(data);
    }

    // ...
}

MyNode.setData(object)가 다시 MyNode.setData(Integer)를 가리키게 되어 다형성을 회복한다.

복구할 수 없는 타입

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

제네릭의 제한

  • 기본형 타입은 제네릭 타입으로 인스턴스화 할 수없다.
    • 박싱 언박싱 내용에서 다루었다.
  • static 멤버는 타입 매개변수를 참조할 수 없다.
  • 매개변수화 된 타입을 형변환 하거나 참조할 수 없다.
  • 매개변수화된 타입의 배열을 만들 수 없다.
  • 매개변수화 된 타입의 객체를 생성, catch, throw 할 수 없다.
  • 클래스는 타입 제거 후 같은 로 타입을 갖게 되는 메서드를 오버로드 할 수 없다.

0개의 댓글