Java2015. 1. 15. 00:54
반응형

 Java 이용해서 개발을 진행하다 보면 잠재적으로 문제를 일으킬 수 있는 경우 소스코드의 경우 컴파일시점에 경고메시지를 출력합니다. 이번 포스팅에서는 그 중에서 Java7부터 추가된 'Type safety: Potential heap pollution via varargs parameter' 에 대해서 알아보도록 합시다. 직역하면 '타입 안전성 : 가변 매개변수를 통한 잠재적인 힙(heap) 오염' 정도로 번역할 수 있을듯 합니다.


public static <T> void test(List<T>... variable);
public static <T> void test(T... variable);

※위처럼 제너릭 타입을 가변 매개변수로 선언했을 경우, 경고 메시지가 출력됩니다.


 일반적인 가변 매개변수의 경우에는 경고가 출력되지 않는것과 비교하여 제네릭의 어떤 특성이 경고를 발생시키는지 알아보기 위해 먼저 제너릭에 대해서 간단하게 알아보도록 합시다. 일반적으로 제너릭(generics)을 사용하는 간단한 예제코드는 아래와 같습니다.


List<String> list1 = new ArrayList<String>();
List<Integer> list2 = new ArrayList<Integer>();

class Sample<T> {
...	
}

 위의 코드는 컴파일 타임에 형(Type)을 검사한후 형 소거자(Type erasure)에 아래와 같이 변환되는 과정을 거쳐서 컴파일 됩니다. 이러한 메커니즘을 통해 기존 코드(제너릭스 도입전의 코드와의 하위 호환성을 유지합니다.) 런타임시에는 타입이 소거됨으로서 하위 호환성을 유지할 수 있는지만 타입 안정성 보장에는 어느정도 손해를 감수할 수 밖에는 없습니다.

List list1 = new ArrayList();
List list2 = new ArrayList();

class Sample {
...	
}


 위에서 언급한 타입 안정성 보장에는 어느정도 손해를 감수할 수 밖에는 없다는 것은 어떤 경우일까요? 바로 형변환(Casting)을 할때 입니다. 그럼 아래의 코드를 통해서 확인해보도록 합시다.


Object to List

Object obj = new Object();
List<String> list = (List<String>)obj;


List to List

List<String> list1 = Arrays.asList(new String[]{"A"});
List<Integer> list2= (List<Integer>)(Object)list1;

for (Integer integer : list2) {
    System.out.println(integer);
}


 두번째 예제 코드는 약간 억지스러운 예제라고 생각하시는 분들도 계실지 모르겠지만 위의 두가지 예제 코드는 정상적으로 컴파일되는 코드입니다. 먼저 Object를 List로 변환하는 첫번째 예제의 경우 런타임에 바로 ClassCastException이 발생하게 됩니다. 두번째 예제 코드는 어떨까요? 두번째 코드의 경우는 1, 2라인만 컴파일하고 실행했을때는 예외가 발생하지 않지만 마지막까지 컴파일후 실행하면 첫번째 코드와 마찬가지로 ClassCastException이 발생합니다.


 첫번째 예제코드는 대입 연산(2번째 라인)에서 바로 예외가 발생하였지만 두번째 예제코드는 1, 2라인만 실행했을 경우는 형 소거자에의해 제너릭 타입이 제거되므로 둘다 List타입으로 변환됨으로서 실제의 타입은 다르더라도 둘다 List형이므로 대입이 가능합니다. 하지만 실제의 타입으로 형변환이 필요한 4라인에 와서 예외가 발생합니다. 물론 위의 코드는 'Type safety: Unchecked cast' 경고를 유발하므로 작성시에 충분히 대응이 가능합니다.






 이제 본 포스팅의 초점인 매개변수에 있어서 나오는 경고에 대해서 살펴보도록 합시다. 위에서 제너릭의 형 소거자가 타입 안정성 보장에 틈을 가져온다고 언급했습니다. 매개변수의 경우 아래와 같은 변환 과정을 거쳐 컴파일이 진행됩니다.


//콜렉션의 경우
public static <T> void test(List<T>... variable);
 ↓
public static <T> void test(List<T>[] variable);
 ↓
public static <T> void test(List[] variable);

//비콜렉션
public static <T> void test(T... variable);
 ↓
public static <T> void test(T[] variable);


 위의 매개변수의 경우 어떻게 타입 안정성을 해칠수 있는지 살펴보도록 합시다.


public static <T> List<T> test(List<T>... variable) {
    Object[] objArr = variable;
    //오브젝트의 배열로 업캐스팅 된 상태이므로 다른 제너릭 타입의 리스트도 대입이 가능합니다.
    objArr [0] = Arrays.asList(new Integer[]{Integer.valueOf(1),Integer.valueOf(2)});
    ...
}
public static <T> void test2(T... variable) {
    //T가 List<String>일 경우 아래처럼 힙 오염이 될 가능성이 있습니다.
    List<Integer> list = (List<Integer> )variable[0];
    list.add(Integer.valueOf(1));
}


 위의 예제에서 매개변수로 전달된 리스트가 메소드 내부에서 오염되었음을 알 수 있습니다. 매개변수가 아닐 경우에 출력되는 'Type safety: Unchecked cast' 경고와 본질적으로는 같은 경고라고 생각되지만 매개변수의 참조 혹은 메소드 호출을 통해서 메소드 외부에도 영향을 미친다는 점에서 별도의 경고를 출력하는 것으로 생각됩니다. 이 경고를 억제하기 위해서는 @SuppressWarnings("unchecked") 혹은 @SafeVarargs라는 1.7에서 추가된 별도의 어노테이션이 사용 가능합니다만 @SafeVarargs어노테이션의 경우는 메소드 외부에 영향을 끼친다는 점이 해소되었다는 것을 강제하기 위해서 static메소드 이거나 final 메소드의 경우에만 어노테이션을 기술할 수 있습니다. 메소드 재정의(Override)를 통한 우회가 가능하기 때문입니다.



이클립스 어노테이션 기술

※이클립스(eclipse)에서 final로 선언된 메소드의 경우 @SafeVarargs어노테이션을 기술할 수 있도록 출력



 이번 포스팅에서 소개한 경고 이외에도 Java컴파일러는 다양한 경고를 출력합니다. 이런 경고의 원인을 잘 이해하고 개발을 진행하는 것이 중요하다고 생각됩니다. 또한 @SuppressWarnings, @SafeVarargs처럼 경고를 억제하는 어노테이션은 단순히 경고 출력을 억제할 뿐이므로 해당 어노테이션을 추가하기 전에 개발자 스스로 런타임에 문제가 없다는 것을 확인한 후 추가하도록 합시다.



Posted by Reiphiel