Item32. Generic과 가변인수를 함께 쓸떄는 신중하라
가변인수에 Generic은 타입 안전하지 않다.
가변인수(varargs) method와 Generic은 JDK 5때 함께 추가되었다.
가변인수 method를 호출하면 가변인수를 담기 위한 배열이 자동으로 하나 만들어진다.
가변인수 type을 실체화 불가 타입으로 선언하면 heap pollution 이 생길 수도 있다는 compile 경고가 뜬다.
아래와 같은 상황에서 heap pollution이 발생할수 있기 떄문이다.
1 | static void dangerous(List<String> ... stringList){ |
매개 가변변수 stringList는 Generic 배열타입이 되고, 배열은 공변이기 떄문에 Object[] 타입으로 받을 수 있다. 따라서 Object 배열에 다른 타입매개변수를 갖는 Generic을 넣는 경우, 값을 꺼낼떄 문제가 생긴다.
@SafeVaraagrs
JDK 7에서 부터 추가되어, Method 작성자가 해당 Method가 타입안전함을 보장해주는 기능을 하며, Generic 가변인수 method 작성자가 client측에 발생하는 경고를 숨길 수 있게 해준다.
Generic 가변인수 method는 다음과 같을떄 타입안전하다고 한다.
- Generic 배열에 아무것도 저장되지 않으면서, 배열의 참조가 외부로 노출되지 않는 경우
다음과 같이 배열의 참조를 반환하는 경우 타입안전하지 않다.
1 | static <T> T[] toArray(T...args){ |
3개의 parameter를 넘겨주고 그중 무작위로 선택된 2개의 배열을 반환받는 method가 있다고 가정했을떄 직접 실행해보면 runtime에 class cast exception이 터진다.
1 | public static void main(String[] args) { |
그이유는 toArray(T..args) method에서 내부적으로 parameter를 받을 Object[] 배열을 만드는 코드를 생성하기 때문이다. 반환된 Object 배열은 client 측에서 compiler에 의해 String 배열로 자동 형변환된다.
이떄 Object[]는 String[] 의 하위타입이 아니기 떄문에 형변환은 실패한다.
예외
@SafeVarargs로 제대로 annotation된 또 다른 varargs method에 Generic 배열을 넘기는 건 안전하다.
Generic 배열 내용의 일부 함수를 호출만 하는 일반 method에 넘기는 것도 안전하다.
다음은 Generic 가변인수 매개변수를 안전하게 사용하는 예제이다.
Generic 배열의 참조값을 반환하지도, Generic 배열에 값을 삼입하지도 않고 있다.
1 |
|
@SafeVarargs 작성 규칙
- Generic이나 매개변수화 타입의 varargs 매개변수를 받는 모든 method에 @SafeVarargs를 달아야 사용자를 헷갈리게 하는 compiler 경고를 제거할 수 있다.
- @SafeVarargs annotation을 달기전에 타입 안전한지 (heap pollution은 없는지) 확인하고, 달아야 한다.
추가로 해당 method를 overriding시에도 타입 안전한지는 보장할수 없으므로, JDK 8에서부터는 overriding 불가능한 static method와 final method에만 붙일 수 있다. JDK9 부터는 private method에도 허용된다.
가변인수 대신 List 사용
Generic 가변인수를 받는 method가 있을떄 타입안전한지 확인하고, overriding할수 없는 상태인지 확인하고 , @SafeVarargs를 붙여주는 방법도 있겠지만 그냥 List 타입으로 변경해주는 해결방안도 있다.
1 | static <T> List<T> flatten(List<List<? extends T>> lists){ |
client에서 flatten method에 값을 전달할떄는 List.of(…) method를 사용하면 된다.
이 방식의 장점은 개발자가 실수로 타입 안전하다고 잘못판단할 가능성이 없으며 , 단점은 client코드가 조금 지저분해진다는 단점을 가지고 있다.
또한 이방식은 이전 예제코드인 타입 불안전한 toArray method를 사용하지 않고, 우회할떄에도 사용가능하다.
1 | static <T> T[] toArray(T...args){ |
이를 List.of(…)로 타입안전하게 변경하면 다음과 같이 변경할 수 있다.
1 | static <T> List<T> pickTwo(T a,T b ,T c){ |
정리
가변인수에 Generic을 넣으면 내부적으로 Generic 배열이 생성되어 heap pollution이 발생하는 등 타입 안전하지 않을수도 있다. 따라서 Generic 배열의 참조를 반환하지 않고, 값도 넣지 않음을 확인하고 @SafeVarargs 을 붙이던지, 가변인수 자체를 제거하고 List로 변경하자.