//Raw type
List names = new ArrayList(); // warning : raw type
names.add("fordevelop");
names.add("me");
names.add(Boolean.FALSE); // not a compilation error!
for(Object o : names){
String name = (String)o; // throw ClassCastException
System.out.println(name);
}
-> java.lang.Boolean 타입은 java.lang.String 타입으로 형변환 불가함
//Generics
List<String> names = new ArrayList<>();
names.add("fordevelop");
names.add("me");
names.add(Boolean.FALSE); // compilation error!
-> String 타입만 저장하도록 컴파일러가 체크해줌
what-is-a-raw-type-and-why-shouldnt-we-use-it
제네릭 클래스 or 제네릭 인터페이스 : 클래스와 인터페이스 선언에 타입 매개변수(type parameter)가 쓰인 것
제네릭 타입(generic type) : 제네릭 클래스와 제네릭 인터페이스를 통틀어 말한 것
제네릭 타입은 매개변수화 타입(parameterized type)을 정의함
ex) List<String> : 원소의 타입이 String인 리스트를 뜻하는 매개변수화 타입
String : 정규 타입 매개변수 E에 해당하는 실제 타입 매개변수
제네릭 타입을 정의하면 그에 대한 로 타입도 함께 정의됨
로 타입(raw type) : 제네릭 타입에서 타입 매개변수를 전혀 사용하지 않을 때
ex) List<E>의 로 타입 : List
타입 선언에서 제네릭 타입 정보가 전부 지워진 것처럼 동작함
//Stamp 인스턴스만 취급함
private final Collection stamps = ...;
//실수로 동전을 넣음
stamps.add(new Coin(...)); //로 타입 사용 시 컴파일 에러 체크 불가
for(Iterator i = stamps.iterator(); i.hasNext(); ){
Stamp stamp = (Stamp)i.next(); //ClassCastException 던짐
stamp.cancel();
}
문제점 : 런타임에야 오류를 알아챌 수 있음. 그러나 런타임에 문제를 겪는 코드와 원인을 제공한 코드가 물리적으로 상당히 떨어져 있을 가능성이 큼. 위의 코드에서처럼 ClassCastException이 발생하면, stamps에 동전을 넣은 지점을 찾기 위해 코드 전체를 훑어봐야 할 수도 있음.
private final Collection<Stamp> stamps = ...;
개선점 : stamps에는 Stamp의 인스턴스만 넣어야 함을 컴파일러가 인지하게 됨. 따라서 stamps에 그외의 타입의 인스턴스를 넣으려 하면, 컴파일 오류가 발생함.
제네릭이 주는 안전성과 표현력을 모두 잃게 됨
기존의 제네릭이 없을 때의 코드와의 호환성을 위해 로 타입을 만들어놓았음
List 같은 로 타입은 사용하면 안되지만, List< Object >처럼 임의 객체를 허용하는 매개변수화 타입은 괜찮음
차이점 :
→ List< Object >는 모든 타입을 허용한다는 것을 컴파일러에게 명확히 전달함
→ 매개변수로 List를 받는 메서드에 List< String > 넘길 수 있음. 매개변수로 List< Object >를 받는 메서드에는 넘길 수 없음.
이유) 제네릭의 하위 타입 규칙에 의해, List< String >은 로 타입인 List의 하위 타입이지만, List< Object >의 하위 타입은 X
즉, List< Object > 같은 매개변수화 타입을 사용할 때와 달리 List 같은 로 타입을 사용하면 안전성을 잃게 됨
public class test1 {
private static void unsafeAdd(List list, Object o) {
list.add(o);
}
public static void main(String[] args) {
List<String> strings = new ArrayList<>();
unsafeAdd(strings, Integer.valueOf(32));
String s = strings.get(0);
}
}
문제상황 :
→ 컴파일은 되지만, 로 타입인 List를 사용해 경고 발생함
→ strings.get(0)의 결과를 형변환하려 할 때 ClassCastException 던짐(Integer -> Object로 upcasting 되었으므로, Object -> Integer로만 downcasting 가능함)
public class test1 {
private static void unsafeAdd(List<Object> list, Object o) {
list.add(o);
}
public static void main(String[] args) {
List<String> strings = new ArrayList<>();
unsafeAdd(strings, Integer.valueOf(32));
String s = strings.get(0);
}
}
문제상황 : List< String >은 List< Object >의 하위 타입이 아니므로, 컴파일 불가능 (런타임 전에 오류 체크 가능함)
static int numElementsInCommon(Set s1, Set s2){
int result = 0;
for(Object o1 : s1)
if(s2.contains(o1)) result++;
return result;
}
문제점 : Set 로 타입(raw type)을 사용해 안전하지 않음
static int numElementsInCommon(Set<?> s1, Set<?> s2){...}
개선점 :
→ 비한정적 와일드카드 타입(unbounded wildcard type) 사용
→ 제네릭 타입을 쓰고 싶지만 실제 타입 매개변수가 무엇인지는 신경 쓰고 싶지 않을 때 < ? > 사용함 (제네릭 타입인 Set< E >의 비한정적 와일드카드 타입은 Set< ? >임)
→ 가장 범용적인 매개변수화 Set 타입
→ raw type보다 안전함
public static void printList(List<Object> list){
for(Object elem : list)
System.out.println(elem + " ");
System.out.println();
}
문제 상황 :
→ printList()의 목적은 타입에 상관없이 List를 출력하는 것이지만, 위의 코드의 경우 해당 목적을 만족하지 못함.
→ Object 인스턴스의 리스트만 출력이 가능함. 즉 List< Integer >, List< Double >, List< String >은 List< Object >의 하위 타입이 아니기 때문에 출력 불가함!
public static void printList(List<?> list){
for(Object elem : list)
System.out.println(elem + " ");
System.out.println();
}
List<Integer> list1 = Arrays.asList(1,2,3);
List<String> list2 = Arrays.asList("1", "2", "3");
printList(list1);
printList(list2);
개선 상황 :
→ 타입에 상관없이, List< A >는 List< ? >의 하위 타입임!
Collection<?> c = new ArrayList<String>();
c.add(new Object()); // 컴파일 에러 발생!
→ 비한정적 와일드 타입 객체에는 null만 삽입 가능함 (null은 모든 타입의 member이기 때문에)
→ 위의 코드를 보면, 참조변수 c의 원소 타입은 unknown type으로 무엇을 나타내는지 명확하지 않음.
→ add() 사용 시 해당 메서드로 전달된 인자들은 unknown type의 하위 타입이어야 하므로 null을 제외한 특정 타입 삽입 불가함
→ class 리터럴에 매개변수화 타입 사용 불가함(배열과 기본 타입은 허용)
ex) List.class, String[].class, int.class는 허용 / List< String >.class, List< ? >.class는 허용 X
→ 런타임에는 제네릭 타입 정보가 지워지므로 로 타입이든 비한정적 와일드카드 타입이든 instanceof는 똑같이 동작함
→ 즉 < ? >는 아무런 역할을 하지 못하므로, 차라리 로 타입으로 쓰는 것이 깔끔함
if(o instanceof Set){ //로 타입
Set<?> s = (Set<?>)o; //와일드카드 타입으로 형변환
...
}
→ 위의 코드를 보면, o의 타입이 Set임을 확인한 다음 와일드카드 타입인 Set< ? >으로 형변환 함(검사 형변환(checked cast)이므로 컴파일러 경고가 뜨지 않음)
→ 컴파일 타임에만 타입을 명시해두고, 런타임에는 타입 정보를 제거(교체)하는 것 (즉 generic type 정보는 컴파일 타임에서만 사용 가능함)
→ 이전 버전의 자바 코드와의 호환성을 위해서 사용됨
→ 자바 컴파일러가 타입 매개변수를 교체함(2가지 경우 존재)
→ Object로 교체함
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; }
...
}
//Replaces it with 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; }
...
}
→ first bound class로 교체함
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; }
...
}
//Replaces it with the first bound class, 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; }
...
}
java tutorials Erasure of Generic Types
java generics questions
참고)
책 이펙티브 자바 3/E