컴파일 타입에만 타입 제약 조건을 정의하고, 런타임에는 타입을 제거한다.
이를통하여 매개변수화 된 타입에 대해 새로운 클래스가 생성되지 않게 하여 런타입 오버헤드 발생을 막는다.
타입 제거프로세스에서 자바 컴파일러는 타입 매개변수를 모두 지우고,
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) { /* ... */ }
타입 제거로 인해서 타입 안정성에 영향이 생기게 된다.
Node
와 MyNode
의 예시를 살펴보자
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