본문 바로가기

STUDY/이펙티브자바

5-6) 제네릭과 가변인수를 함께 쓸 때는 신중하라

제네릭과 가변인수

 

// 제네릭과 가변인수를 함께 사용하면 컴파일러가 경고를 보낸다
// Type safety: Potential heap pollution via varargs parameter stringList
public static void myFunc(List<String>... stringLists)
    List<Integer> intList = List.of(1);
    Object[] objects = stringLists;
    objects[0] = intList; // 힙 오염 발생
    String s = stringLists[0].get(0); // ClassCastException 런타임 에러 발생
}

가변인수와 제네릭은 자바5때 함께 추가되었으나 함께 사용하기에는 까다롭다. 제네릭과 가변인수를 함게 사용하면 컴파일러는 힙 오염이 발생할 수 있다는 경고를 보낸다. 그리고 myFunc의 내용을 보면 실제로 힙 오염이 발생한다. 그리고 실제로 코드를 실행해보면 힙 오염 때문에 ClassCastException 런타임 에러가 발생한다.

 

// myFunc를 호출할 때 마다 경고를 띄우기 때문에 사용하는 쪽에서 매번 SuppressWarnings 어노테이션을 달아야한다.
@SuppressWarnings("unchecked")
public static void main(String[] args) {
    myFunc(List.of("hello world"));
}

그리고 컴파일러는 제네릭과 가변인수를 함께 사용한 myFunc 뿐만 아니라, 해당 함수를 호출하는 모든 곳에도 경고를 띄운다. myFunc 개발자는 자신의 함수를 호출하는 곳에 나타나는 경고를 없애줄 수 없다. 결국 함수를 호출하는 개발자가 직접 @SuppressWarnings 어노테이션을 달아야한다.

 

 


@SafeVarargs

 

// 제네릭과 가변인수를 함께 사용하는 함수에서
// 1. 매개변수 배열을 수정하지 않고
// 2. 외부에서도 수정할 수 없도록 반환하지 않으면
// 힙오염으로부터 안전하다고 할 수 있다.
@SafeVarargs
public static void myFunc(List<String>... stringLists)
    ...
}

자바 7에서부터는 제네릭과 가변인수를 함께 사용한 함수의 개발자가 해당 함수를 호출하는 곳의 경고를 없앨 수 있는 @SafeVarargs라는 어노테이션이 생겼다. 해당 함수가 힙오염으로부터 안전하다면 @SafeVarargs 어노테이션을 달면 된다. 힙 오염은 매개변수 배열을 함수 내부에서 수정하고 덮어쓰다가 생긴다. 따라서 함수 내부에 매개변수 배열의 데이터를 수정하는 코드를 제거해야 한다. 그리고 매개변수 배열을 반환하면 외부에서 힙 오염을 일으킬 수 있으므로 반환하지 않도록 한다. 이 두개가 만족한다면 @SafeVarargs 어노테이션을 달면 된다.

 

 


 가변인수를 사용하지 않도록 변경

 

public static void myFunc(List<List<String>> stringLists) {
	...
}

// a, b, c를 가변인수로 넘기는 것이 아니라 List에 담아서 전달
myFunc(List.of(a, b, c));

@SafeVarargs 만이 정답은 아니다. 가변인수 사용하지 않는 것도 고려해볼 수 있다. 가변인수란 매개변수로 정해지지 않은 개수의 객체를 받겠다는 것이다. 그리고 이는 List 타입으로 전달받는것으로 대채할 수 있다. 이렇게 하면 컴파일러가 타입 안전성을 검증 할 수 있다. 다만 코드의 가독성이 떨어지고 성능이 저하될 수 있다.

 

 

728x90