JAVA7에서는 Generic을 사용할 때 아래 처럼 생성자에 해당 타입을 상세하게 명시하지 않아도 된다.
HashMap<String?Integer> map=new HashMap<String,Integer>();
// 아래는 JAVA7에서 타입을 상세하게 적어주지 않아도 되는 예
HashMap<String,Integer> map = new HashMap<>();
왜냐하면, 이미 변수를 선언할 때 필요한 타입들을 지정해 놓았기 때문이다. 그래서 "<>"로 표시하면 컴파일러가 알아서 해당 생성자 타입을 지정해버린다.
이처럼 편리한 다이아몬드는 편한 만큼 다음과 같은 제약이 있다.
package f.generic;
public class GenericClass <X> {
private X x;
private Object o;
public <T> GenericClass (T t) {
this.o=t;
System.out.println("T type="+t.getClass().getName());
}
public void setValue(X x) {
this.x=x;
System.out.println("X type="+x.getClass().getName());
}
}
package f.generic;
public class TypeInference {
public static void main(String args[]) {
TypeInference type = new TypeInference();
type.makeObject1();
}
// 제네릭하면서도 제네릭하지 않은 객체 생성시
public void makeObject1(){
GenericClass<Integer> generic1 = new GenericClass<>("String");
generic1.setValue(999);
}
// 다이아몬드 미 지정시 컴파일 경고 발생
public void makeObject2(){
GenericClass<Integer> generic1 = new GenericClass("String");
generic1.setValue(999);
}
// 다이아몬드를 안쓰고 명시적으로 지정할 때
public void makeObject3(){
GenericClass<Integer> generic1 = new GenericClass<Integer>("String");
generic1.setValue(999);
}
// X 타입을 다이아몬드로 선언할 때
public void makeObject4(){
GenericClass<Integer> generic1 = new <String> GenericClass<>("String");
generic1.setValue(999);
}
}
X와 T, 두가지의 제너릭한 타입이 선언되어있는 클래스이다. 여기서 makeObject4 함수를 컴파일하게 되면 아래와 같은 에러가 발생한다.
error: cannot infer type arguments for GenericClass
GenericClass generic1 = new GenericClass<>("String");
reason: cannot use '<>' with explicit type parameters for constructor
where X is a type-variable:
X extends Object declared in class GenericClass
1 error
이렇게 명시적으로 타입 T에 대해서 선언한 상태에서, 타입 X에 대해서는 다이아몬드로 선언하여 컴파일러에게 맡겨버리면 컴파일이 정상적으로 되지 않는다. 따라서, 생성자에 있는 new와 클래스 이름 사이에 타입 이름을 명시적으로 두려면, 다이아몬드를 사용하면 안된다는 것을 기억하자.
자바의 제너릭을 사용하면서, 발생 가능한 문제 중 하나가 "reifiable 하지 않은 vararge 타입(non reifiable varargs type)"이다. 이 문제는 자바에서 제너릭을 사용하지 않는 버전과의 호환성을 위해서 제공했던 기능 때문에 발생한다.
reifiable : 실행시에도 타입정보가 남아있는 타입을 의미
non reifiable : 컴파일시 타입 정보가 손실되는 타입을 의미
자바에서 varargs를 사용하면 메소드에 여러 인자를 쉽게 넘길 수 있다. 예를 들어, 여러 리스트를 하나로 합치고 싶을 때 varargs를 사용하는 것이 매우 편리한데, 이 리스트들이 제네릭 타입일 경우 문제가 발생할 수 있다.
public class VarargsExample {
public static <T> List<T> mergeLists(List<T>... lists) {
List<T> mergedList = new ArrayList<>();
for (List<T> list : lists) {
mergedList.addAll(list);
}
return mergedList;
}
}
위 코드에서 mergedLists 메소드는 제너릭 타입의 리스트를 varargs(가변 매개변수) 인자로 받고 있다. 이 소스코드는 컴파일은 되지만, 경고 메시지를 아래와 같이 출력한다.
Note: VarargsExample.java uses unchecked or unsafe operations.
Note: Recompile with -Xlint:unchecked for details.
왜냐하면, 'List... lists'는 내부적으로 List[]로 처리되는데, 제네릭 타입의 배열은 타입 안전성을 보장받지 못하기 때문이다. 배열은 런타임에 타입 정보를 유지하지만, 제네릭은 타입 정보가 런타임에 사라지므로(non-reifiable), 잘못된 타입의 객체가 배열에 추가될 수 있다. 이런 상황을 힙 오염(heap pollution)이라고 한다.
이러한 문제의 해결책은 자바7에서 도입된 @SafeVararge 어노테이션을 사용하면 된다. 이 어노테이션은 개발자가 해당 메소드의 사용이 타입 안전하다고 명시적으로 선언할 수 있도록 해준다. 이는 컴파일러에게 불필요한 경고를 발생시키지 않도록 지시한다. 위 예제에서 mergeLists 메소드위에 @SafeVarages를 추가시켜주면 해결된다.
이 애노테이션은 다음과 같은 경우에서만 사용할 수 있다.
- 가변 매개 변수를 사용
- final 이나 static으로 선언되어야 함
그리고 해당 애노테이션은 아래의 경우에는 컴파일러에서 경고가 발생한다.
- 가변 매개 변수가 reifiable 타입
- 매소드 내에서 매개 변수를 다른 변수에 대입하는 작업을 수행하는 경우