티스토리 뷰

Java/Effective Java

[Item32] varargs는 신중히

ryumodern 2022. 3. 11. 19:51

varargs란 메서드 인수의 개수를 클라이언트 측에서 결정할 수 있다는 점에서 개꿀이다

인수를 여러 개 넘기면 내부적으로는 배열을 만들어 담아두고 사용한다

바로 이 부분에서 문제가 되는데 가변 인수를 제네릭 타입으로 넘긴다면 실체화되지 않는 타입으로

배열을 만들기 때문에 타입 안전성이 보장되지 않아 possible heap pollution 경고가 뜬다

매개변수 타입을 변환하지 않고 사용한다면 문제없을 수 있으나 만약 얘를 메서드 내부에서 형 변환하거나

다른 타입을 참조하는 경우엔 ClassCastException이 터지게 된다

 

힙 오염의 의미는 아래 블로그 글을 참고해보자

아래의 예시는 <Integer>로 선언해놓고 Double 타입의 요소를 갖고 있어 힙 오염이 발생했다

 

https://arbitrary-but-fixed.net/teaching/java/2018/02/01/java-generic-arrays-varargs.html

 

 

이 전에 제네릭과 배열을 언급했을 때는 아예 생성조차 되지 않는다고 했는데 여기서는 왜 경고로만 끝날까?

단순히 이 방식이 유용하기 때문이라고 한다, 분명 모순이지만 언어 차원에서 받아들였다

그렇다면 나도 군말 없이 받아들이겠다

책에 등장한 예시를 자바17에 등장한 switch pattern matching 형태로 바꾼 것이다

static <T> T[] pickTwo(T a, T b, T c) {
  return switch (ThreadLocalRandom.current().nextInt(3)) {
    case 0 -> toArray(a, b);
    case 1 -> toArray(a, c);
    case 2 -> toArray(b, c);
    default -> throw new AssertionError();
  };
}

 

컴파일러는 이 메서드를 보고 T 타입의 인자 둘을 toArray()에 넘기는데

toArray()에서 인자의 타입을 추론할 수 없기 때문에 모든 타입을 받을 수 있는 Object로 바꾸어 Object[]로 만들고 반환한다

 

pickTwo를 사용하는 시점에는 pickTwo에 String을 넘기면 String[]으로 반환되나

사실 Object[]가 반환된 것이고 그 내부에서 암묵적인 형 변환이 이루어진다

따라서 Object -> String 변환 과정에서 ClassCastException을 마주치게 된다

 

@SafeVaragrs 애노테이션이 등장하기 전인 자바7 이전까지는 경고를 처리할 책임까지 클라이언트에게 있었다

그래서 @SuppressWarnings("unchecked")로 숨기거나 그냥 내버려 두었다고 한다

자바 7부터@SafeVarargs로 작성자가 타입 안전성을 보장할 수 있게 됐는데

마음 가는 대로 작성하고 냅다 붙여서 해결되는 것은 아니고 반드시 지켜야 할 제약 사항이 있다

1. 가변 인수를 받을 때 생성되는 배열에 아무것도 새로 저장하지 않아야 한다

2. 가변 인수를 받을 때 생성되는 배열을 반환하지 않아야 한다

 

1번 조건은 실체화되지 않을 타입을 건드리지 않은 것이니 경고는 뜨더라도 변한 게 없으니 안전하다

2번 조건은 리턴 타입이 컴파일 타임에 결정되므로 타입 정보를 올바르게 추론할 수 없으니 반환해서는 안 된다

이 두 조건을 지켰다면 사용자가 경고를 신경 쓰지 않고 사용할 수 있게 @SafeVarargs를 붙여주자

 

이 보다 더 쉽고 안전하게 만들려면 인수 자체를 리스트로 사용하는 것이다

자바9부터 사용 가능한 List.of()를 이용해 배열을 넘기는 대신 리스트로 넘겨 타입 안전성을 보장한다

배열 사용과 비교해봤을 때 타입 안전성은 높아지나 가독성이 구려질 수 있고, 성능이 저하될 수 있다는 단점이 있다

이 역시 전에 말했듯 대부분의 상황은 배열이냐 리스트냐 보다 성능을 가를 요소가 넘치기에 웬만하면 리스트 사용하자

자바8에도 Arrays.asList()를 사용하면 같은 방식으로 이용할 수 있다

static <T> List<T> pickTwo(T a, T b, T c) {
  return switch (ThreadLocalRandom.current().nextInt(3)) {
    case 0 -> List.of(a, b);
    case 1 -> List.of(a, c);
    case 2 -> List.of(b, c);
    default -> throw new AssertionError();
  };
}

 

Arrays.asList()

요놈 역시 @SafeVarargs가 붙어있기에 경고를 신경 쓰지 않고 사용할 수 있다

자바8 지원이 빨리 끝나야 실무에서도 버전 업을 할텐데 2025년까지 지원하겠다고 한다, 세상에 이런 일이..

2025년까지 존버하다가 11, 17 스킵하고 자바23으로 가는 거 아닌가 모르겠다

댓글
링크
글 보관함
«   2024/05   »
1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31
Total
Today
Yesterday