컴파일러는 제네릭 타입을 이용하여 소스파일을 검사하고, 필요한 곳에
형변환을 추가한 뒤에제네릭 타입을 제거합니다.
컴파일 된 .class 파일에는 제네릭에 대한 정보가 없습니다.
따라서 제네릭을 사용할 때 다음과 같은 제약이 있습니다.
static 멤버에 제네릭을 사용할 수 없습니다.- 제네릭으로
배열 생성또는객체 생성이 불가능합니다.instanceof우항에 제네릭을 사용할 수 없습니다. 참조값이 와야하는 좌항은 당연하게도 안됩니다.- 타입 파라미터의
Class 객체또한 접근(T.class)이 불가능합니다.
- 제네릭 타입의 상속 계층에서의
범위를 제한하기 위해서extends키워드를 사용합니다.인터페이스에 대한 제약 또한extends를 사용합니다.'&'를 이용해multi bound가 가능합니다.
multi-catch block의'|'와 마찬가지로 논리 연산자가 아니며, AND 의 의미로 사용된 것입니다.
제네릭 클래스에서의 타입 변수와제네릭 메서드에서의 타입 변수는독립적입니다.- 다만 두 타입 변수의 이름이 겹치는 경우 먼 변수(제네릭 클래스 타입 변수)가 가려져 사용할 수 없습니다.
shadowing을 피하고,가독성을 높이기 위해 두 변수는이름을 다르게지어야 합니다.
public class GenericLevelTest {
public static void main(String[] args) {
MyClass1 myClass1 = new MyClass1();
GenericClass<MyClass1> gc = new GenericClass<>();
MyClass2 myClass2 = new MyClass2();
Object o = gc.genericMethod(myClass1, myClass2); // nearest common ancestor -> Object
System.out.println(o instanceof MyClass1);
System.out.println(o instanceof MyClass2);
}
}
class GenericClass<T> {
public <T> T genericMethod(T classLevelInput, T methodLevelInput) { // shadowing
System.out.println(classLevelInput.getClass()); // class MyClass1
System.out.println(methodLevelInput.getClass()); // class MyClass2
return classLevelInput;
}
}
class MyClass1 {}
class MyClass2 {}
와일드카드는 제네릭 타입 시스템 내에서 여러관련 타입을묶어다룰 수 있게 해주는 메커니즘입니다.- 와일드카드는
타입 안전성을유지하면서 제네릭 타입 사용의유연성을 높여줍니다.- 제네릭에
다형성기능을 강화했다고 생각해도 좋을 것 같습니다.
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
public class GenericsTest {
public static void main(String[] args) {
// FruitBox<? extends Fruit> fruitBox = new FruitBox<>();
FruitBox<Fruit> fruitBox = new FruitBox<>();
fruitBox.add(new Apple());
fruitBox.add(new Grape());
FruitBox<Apple> appleBox = new FruitBox<>();
appleBox.add(new Apple());
appleBox.add(new Apple());
FruitBox<Grape> grapeBox = new FruitBox<>();
grapeBox.add(new Grape());
grapeBox.add(new Grape());
FruitComparator fruitComparator = new FruitComparator();
// signature of Collections.sort()
// public static <T> void sort(List<T> list, Comparator<? super T> c)
Collections.sort(fruitBox.getList(), fruitComparator);
Collections.sort(appleBox.getList(), fruitComparator);
Collections.sort(grapeBox.getList(), fruitComparator);
System.out.println(Juicer.makeJuice(fruitBox)); // wildcard makes it flexible
System.out.println(Juicer.makeJuice(appleBox));
System.out.println(Juicer.makeJuice(grapeBox));
}
}
class Box<T> {
// T[] item = new T[5]; // cannot be instantiated directly
// T item = new T(); // cannot be instantiated directly
List<T> list = new ArrayList<>();
void add(T item) { list.add(item); }
public List<T> getList() { return list; }
// public void errorTest(Fruit fruit) {
// boolean b = fruit instanceof T; // compiler error
// Class<T> c = T.class; // cannot access class object of a type parameter
// }
}
class FruitBox<T extends Fruit> extends Box<T> {}
class Fruit { public String toString() { return "Fruit"; } }
class Apple extends Fruit { public String toString() { return "Apple"; } }
class Grape extends Fruit { public String toString() { return "Grape"; } }
class FruitComparator implements Comparator<Fruit> {
public int compare(Fruit o1, Fruit o2) {
return o1.toString().compareTo(o2.toString());
}
}
class Juice {
String name;
Juice(String name) { this.name = name + "Juice"; }
public String toString() { return name; }
}
class Juicer {
static <T extends Fruit> Juice makeJuice(FruitBox<T> box) { // wildcard
StringBuilder tmp = new StringBuilder();
for (Fruit fruit : box.getList()) {
tmp.append(fruit).append(" ");
}
return new Juice(tmp.toString());
}
}
- 제네릭 타입과 원시 타입간에는 언제나 형변환이 가능하지만 경고가 발생합니다.
제네릭 클래스에 타입 매개변수로와일드 카드까지 들어간 경우에형변환을 생각하기 굉장히 어려울 수 있습니다.
Optional클래스소스 코드의 일부를 확인해봅시다.private static final Optional<?> EMPTY = new Optional<>(); public static<T> Optional<T> empty() { Optional<T> t = (Optional<T>) EMPTY; return t; }
Optional<?> EMPTY = new Optional<>();는
Optional<? extends Object> EMPTY = new Optional<Object>();가 생략된 것입니다.
즉Optional<Object>->Optional<?>->Optional<T>의 순서로 형변환 되었습니다.
이렇게 여러 단계에 거쳐 형변환 한 것은
Optional<Object>->Optional<T>
이렇게 바로 형변환이 불가능 하기 때문입니다.
- 와일드 카드가 들어간 형변환 문제는 일반적인 형변환을
범위의 문제로확장했다고 생각하면 편합니다.타입 매개변수에와일드 카드가 들어가면잠재적으로 지정한범위를 모두 가질수 있는 타입이라고 보고,
그렇게 선언된참조변수에는범위에 포함되는타입만대입될 수 있습니다.- 따라서 와일드 카드를 매개변수로 갖는 타입도
범위를 벗어날 수 있는 경우형변환이불가능합니다.- 단
up-casting될가능성이 있다면수동적인 형변환을 통해 대입이 가능합니다.다음 코드를 통해 여러 경우를 확인해 볼 수 있습니다.
class GenericClazz<T> {}
class GrandParent {}
class Parent extends GrandParent {}
class Child extends Parent {}
class GrandChild extends Child {}
public class CastingTest {
public static void main(String[] args) {
// GenericClazz<? extends Object> objectE = null;
GenericClazz<?> objectE = null;
GenericClazz<? extends GrandParent> grandParentE = null;
GenericClazz<? extends Parent> parentE = null;
GenericClazz<? extends Child> childE = null;
GenericClazz<? extends GrandChild> grandChildE = null;
GenericClazz<? super GrandChild> grandChildS = null;
GenericClazz<? super Child> childS = null;
GenericClazz<? super Parent> parentS = null;
GenericClazz<? super GrandParent> grandParentS = null;
GenericClazz<? super Object> objectS = null;
objectE = grandParentE; // OK
objectE = parentE; // OK
objectE = childE; // OK
objectE = grandChildE; // OK
objectE = grandChildS; // OK
objectE = childS; // OK
objectE = parentS; // OK
objectE = grandParentS; // OK
objectE = objectS; // OK
grandParentE = (GenericClazz<? extends GrandParent>) objectE; // consider up-casting
grandParentE = parentE; // OK
grandParentE = childE; // OK
grandParentE = grandChildE; // OK
grandParentE = (GenericClazz<? extends GrandParent>) grandChildS; // consider up-casting
grandParentE = (GenericClazz<? extends GrandParent>) childS; // consider up-casting
grandParentE = (GenericClazz<? extends GrandParent>) parentS; // consider up-casting
grandParentE = (GenericClazz<? extends GrandParent>) grandParentS; // consider up-casting
// grandParentE = (GenericClazz<? extends GrandParent>) objectS; // no possibility
parentE = (GenericClazz<? extends Parent>) objectE; // consider up-casting
parentE = (GenericClazz<? extends Parent>) grandParentE; // consider up-casting
parentE = childE; // OK
parentE = grandChildE; // OK
parentE = (GenericClazz<? extends Parent>) grandChildS; // consider up-casting
parentE = (GenericClazz<? extends Parent>) childS; // consider up-casting
parentE = (GenericClazz<? extends Parent>) parentS; // consider up-casting
// parentE = (GenericClazz<? extends Parent>) grandParentS; // no possibility
// grandParentE = (GenericClazz<? extends GrandParent>) objectS; // no possibility
childE = (GenericClazz<? extends Child>) objectE; // consider up-casting
childE = (GenericClazz<? extends Child>) grandParentE; // consider up-casting
childE = (GenericClazz<? extends Child>) parentE; // consider up-casting
childE = grandChildE; // OK
childE = (GenericClazz<? extends Child>) grandChildS; // consider up-casting
childE = (GenericClazz<? extends Child>) childS; // consider up-casting
// childE = (GenericClazz<? extends Child>) parentS; // no possibility
// childE = (GenericClazz<? extends Child>) grandParentS; // no possibility
// childE = (GenericClazz<? extends Child>) objectS; // no possibility
grandChildE = (GenericClazz<? extends GrandChild>) objectE; // consider up-casting
grandChildE = (GenericClazz<? extends GrandChild>) grandParentE; // consider up-casting
grandChildE = (GenericClazz<? extends GrandChild>) parentE; // consider up-casting
grandChildE = (GenericClazz<? extends GrandChild>) childE; // consider up-casting
grandChildE = (GenericClazz<? extends GrandChild>) grandChildS; // consider up-casting
// grandChildE = (GenericClazz<? extends GrandChild>) childS; // no possibility
// grandChildE = (GenericClazz<? extends GrandChild>) parentS; // no possibility
// grandChildE = (GenericClazz<? extends GrandChild>) grandParentS; // no possibility
// grandChildE = (GenericClazz<? extends GrandChild>) objectS; // no possibility
grandChildS = (GenericClazz<? super GrandChild>) objectE; // consider up-casting
grandChildS = (GenericClazz<? super GrandChild>) grandParentE; // consider up-casting
grandChildS = (GenericClazz<? super GrandChild>) parentE; // consider up-casting
grandChildS = (GenericClazz<? super GrandChild>) childE; // consider up-casting
grandChildS = (GenericClazz<? super GrandChild>) grandChildE; // consider up-casting
grandChildS = childS; // OK
grandChildS = parentS; // OK
grandChildS = grandParentS; // OK
grandChildS = objectS; //OK
childS = (GenericClazz<? super Child>) objectE; // consider up-casting
childS = (GenericClazz<? super Child>) grandParentE; // consider up-casting
childS = (GenericClazz<? super Child>) parentE; // consider up-casting
childS = (GenericClazz<? super Child>) childE; // consider up-casting
// childS = (GenericClazz<? super Parent>) grandChildE; // no possibility
childS = (GenericClazz<? super Child>) grandChildS; // consider up-casting
childS = parentS; // OK
childS = grandParentS; // OK
childS = objectS; // OK
parentS = (GenericClazz<? super Parent>) objectE; // consider up-casting
parentS = (GenericClazz<? super Parent>) grandParentE; // consider up-casting
parentS = (GenericClazz<? super Parent>) parentE; // consider up-casting
// parentS = (GenericClazz<? super Parent>) childE; // no possibility
// parentS = (GenericClazz<? super Parent>) grandChildE; // no possibility
parentS = (GenericClazz<? super Parent>) grandChildS; // consider up-casting
parentS = (GenericClazz<? super Parent>) childS; // consider up-casting
parentS = grandParentS; // OK
parentS = objectS; // OK
grandParentS = (GenericClazz<? super GrandParent>) objectE; // consider up-casting
grandParentS = (GenericClazz<? super GrandParent>) grandParentE; // consider up-casting
// grandParentS = (GenericClazz<? super GrandParent>) parentE; // no possibility
// grandParentS = (GenericClazz<? super GrandParent>) childE; // no possibility
// grandParentS = (GenericClazz<? super GrandParent>) grandChildE; // no possibility
grandParentS = (GenericClazz<? super GrandParent>) grandChildS; // consider up-casting
grandParentS = (GenericClazz<? super GrandParent>) childS; // consider up-casting
grandParentS = (GenericClazz<? super GrandParent>) parentS; // consider up-casting
grandParentS = objectS; // OK
objectS = (GenericClazz<? super Object>) objectE; // consider up-casting
// objectS = (GenericClazz<? super Object>) grandParentE; // no possibility
// objectS = (GenericClazz<? super Object>) parentE; // no possibility
// objectS = (GenericClazz<? super Object>) childE; // no possibility
// objectS = (GenericClazz<? super Object>) grandChildE; // no possibility
objectS = (GenericClazz<? super Object>) grandChildS; // consider up-casting
objectS = (GenericClazz<? super Object>) childS; // consider up-casting
objectS = (GenericClazz<? super Object>) parentS; // consider up-casting
objectS = (GenericClazz<? super Object>) grandParentS; // consider up-casting
}
}