제네릭(Generic)
- 클래스나 메소드 내부에서 사용되는 객체의 안정성을 높일 수 있다.
- 반환값에 대한 타입 변환 및 타입 검사에 들어가는 노력을 줄일 수 있다.
Raw Type
class MyArray<T> {
T element;
void setElement(T element) { this.element = element; }
T getElement() { return element; }
}
T
: 타입 변수(type variable). 임의의 참조형 타입을 의미한다.T
뿐만 아니라 어떠한 문자를 사용해도 상관 없다.,
로 구분하여 명시할 수 있다.MyArray<Integer> myArr = new MyArray<Integer>();
MyArray<Integer> myArr = new MyArray<>(); // Java SE 7부터 가능함.
import java.util.*;
class LandAnimal { public void crying() { System.out.println("육지동물"); } }
class Cat extends LandAnimal { public void crying() { System.out.println("냐옹냐옹"); } }
class Dog extends LandAnimal { public void crying() { System.out.println("멍멍"); } }
class Sparrow { public void crying() { System.out.println("짹짹"); } }
class AnimalList<T> {
ArrayList<T> al = new ArrayList<T>();
void add(T animal) { al.add(animal); }
T get(int index) { return al.get(index); }
boolean remove(T animal) { return al.remove(animal); }
int size() { return al.size(); }
}
AnimalList<LandAnimal>
에 추가할 수 있다
public class Generic01 {
public static void main(String[] args) {
AnimalList<LandAnimal> landAnimal = new AnimalList<>(); // Java SE 7부터 생략가능함.
landAnimal.add(new LandAnimal());
landAnimal.add(new Cat());
landAnimal.add(new Dog());
// landAnimal.add(new Sparrow()); // 오류가 발생함.
for (int i = 0; i < landAnimal.size() ; i++) {
landAnimal.get(i).crying();
}
}
}
구체화 타입(reifiable type)
: 자신의 타입 정보를 런타임에도 알고 있는 것비구체화 타입(non-reify type)
: 런타임에는 소거(erasure)가 되기 때문에 컴파일 타임보다 정보를 적게 가지는 것소거(erasure)
✋ Unbounded Type(<?>, <T>)
은 Object
로 변환한다.
// 컴파일 할 때 (타입 소거 전)
public class Test<T> {
public void test(T test) {
System.out.println(test.toString());
}
}
// 런타임 때 (타입 소거 후)
public class Test {
public void test(Object test) {
System.out.println(test.toString());
}
}
✋ Bound Type(<E extends Comparable>)
의 경우 Object
가 아닌 Comparable
로 변환한다.
// 컴파일 할 때 (타입 소거 전)
public class Test<T extends Comparable<T>> {
private T data;
public T getData() {
return data;
}
public void setData(T data) {
this.data = data;
}
}
// 런타임 때 (타입 소거 후)
public class Test {
private Comparable data;
public Comparable getData() {
return data;
}
public void setData(Comparable data) {
this.data = data;
}
}
✋ 확장된 제네릭 타입에서 다형성 보존을 위해 어떠한 클래스나, 인스턴스를 상속 혹은 구현할때 bridge method
를 생성한다.
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 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);
}
// ...
}
✋ 제네릭 타입을 사용할 수 있는 일반 class, interface, method에만 소거 규칙을 적용한다.
✋ 타입 안정성 측면에서 필요하면 type casting
을 넣는다.