Item32 - 제네릭과 가변인수를 함께 쓸 때는 신중하라
간단 요약
- 가변인자를 갖는 메서드는 배열을 사용해서 인자들을 받아온다.
- 비 구체화 타입을 가변인자로 사용하면 warning이 뜬다.
- 우선 비 구체화 타입을 배열의 원소로 갖는 배열 생성식은 컴파일 에러가 발생함
- 그렇다면 가변인자로 비 구체화 타입을 사용하게 되는 경우는 어떻게 되는가?
- warning 메시지 없애는 방법 @SafeVarargs 애노테이션을 사용한다.
- 가변인자로 제네릭을 사용하고 있지만 type-safe한 메서드란?
- type-safe하지 않은 메서드는 뭘까?
요약
- 제네릭과 가변인수는 함께 사용하면 타입 안정성이 깨질 수 있다.
- 그이유는 배열은 공변이면서 구체적인 타입이지만 제네릭은 불공변이면서 런타임 시에 타임 정보가 소거되기 때문에 비 구체적 타입이라고 할 수 있다.
- 물과 기름 같은 놈들이라 잘 혼용되지 않아!!!
- 타입 안정성을 깨지 않는 경우에 한해서, 제네릭과 가변인수를 잘 사용하면 유용하게 쓰일 순 있어서 컴파일 에러 대신 에러 메세지를 띄우는 식으로 처리가 된다.
- 제네릭과 가변인수를 함께 사용하지만 메서드가 타입 안전한 경우라면?
- @SafeVaragrs 애노테이션을 붙이자. warning 메시지를 없애도록 !
- 그래서 타입 안전한 메서드가 뭔디..?
- 메서드 내에서 어떤 배열에 아무것도 저장하지 않고,
- 배열의 참조가 밖으로 노출되지 않는다면 타입 안전하다고 한다.
- 제네릭과 가변인수를 함께 써야 한다면?
- 가변인수를 List로 대체하는건 어떨지 고민하자.
1. 가변인자를 갖는 메서드는 배열을 사용해서 인자들을 받아오게 된다.
- 가변인자를 갖는 메소드를 매번 호출할 때마다 가변인자를 저장할 배열이 생성됨

2. 비 구체화 타입을 가변인자로 사용하면 warning이 뜬다.
- 배열은 구체화 타입(reified type) 제네릭의 경우 비 구체화 타입(non-reified type)이다.
- 비 구체화 타입? 컴파일 시보다 런타임 시에 더 적은 정보를 갖는 것을 말함
- E, List<E>, List<String> - 비 구체화 타입
- 비 구체화 타입을 배열의 원소로 갖는 배열 생성식은 컴파일 에러가 발생한다.
- 배열은 구체화, 제네릭은 비 구체화 타입이기 때문에 둘은 잘 혼용되지 않는다.
- new List<E>[], new List<String>[], new E[]와 같은 배열 생성식을 사용할 수 없다.
- 컴파일 시에 제네릭 배열 생성 에러가 발생한다.


- 만약 컴파일 에러가 발생하지 않는다면?
- 원소로 비 구체화 타입 List<String> 을 사용하는 배열을 생성함
- 원래 여기서 컴파일 에러가 발생하게된다.
- Integer List 생성, 초기화
- Object[] 타입의 참조변수로 List<String>[] 인스턴스를 가리킴
- 이게 가능한 이유는?
- 배열은 불변이다 & 업캐스팅의 경우 명시적인 타입 캐스팅이 필요 없다.
- List<String>은 Object의 자식
- List<String>의 배열은 Object 배열의 자식
- 서브 클래스의 객체인 List<String>의 인스턴스가 슈퍼 클래스 타입 Object 배열로 형변환 되는 것(=업캐스팅)은 명시적인 타입 캐스팅이 필요 없다.

- List<String> 을 Object[]의 요소로 저장
- 이게 가능한 이유는 또 뭘까?
- 제네릭이 소거자에 의해서 구현되는 비 구체화 타입이기 때문이다.

- 컴파일러 입장에서는 stringLists[0] 는 List<String> 이고 stringLists[0].get(0) 은 String이 나올 것이라고 생각하고 있다. 그래서 컴파일러는 읽어온 요소를 String으로 캐스팅 하는 코드를 생성하게 되는데, 실제로 읽은 요소는 Integer 객체이기 때문에 ClassCastException 런타임 예외가 발생하게 된다.
- 명시적인 캐스트가 없는 라인에서 ClassCastException 예외가 발생해버릴 수 있다.
- 이러한 위험성 때문에 비 구체화 타입을 배열로 원소로 가지는 배열 생성식을 사용하는 경우 컴파일 에러가 발생한다.
그렇다면 가변인자로 비 구체화 타입을 사용하게 되는 경우는 어떻게 될까?
- 비 구체화 타입을 배열의 원소로 사용하게 되면 컴파일 에러가 발생하게 된다.
- 가변 인자를 가지는 메소드는 내부적으로 배열을 생성한다고 했는데,
- 그렇다면 가변 인자로 비 구체화 타입인 제네릭을 넘길때도 컴파일 에러가 발생하는가?
- warning만 발생하게 된다.

- 힙 오염이란?
- 매개변수화 타입의 변수가 타입이 다른 객체를 참조하면 힙 오염이 발생한다.
- 어떤 이유로 컴파일 에러를 발생시키지 않을까?
- 가변인자로 제네릭 타입이나 매개변수화 타입을 사용하는 경우 유용하게 쓰일 수 있기 때문이다.
- 메소드가 타입안전한 경우에 한해서 잘 쓰면 유용하지만 잘못사용하면 ClassCastException을 발생시킬 수 있다.

- warning 메시지 없애기 @SafeVarargs
3. 가변인자로 제네릭을 사용 하고 있지만 type-safe한 메서드는?
- 가변인자를 사용함으로써 만들어지는 배열에 어떠한 것도 덮어쓰지 않고
- 그 배열을 가리키는 참조 변수를 직간접적으로 만들지 않는다면
이러한 메서드를 type-safe하다고 이야기한다.
즉 가변인자가 여러 인자들을 넘겨오는데만 사용되는 메서드를 일컫는다.
그리고 이러한 메서드에는 안심하고 @SafeVarargs 애노테이션을 붙여줄 수 있다.
type-safe 하지 않는 메서드의 예
- 가변인자로 넘어온 여러 데이터들을 하나의 배열로 묶어주는 메서드이다.
- 이 메서드는 가변인자 배열을 가리키는 참조변수를 리턴하고 있기 때문에 힙 오염을 발생시킬 가능성이 있다.
static <T> T[] toArray(T... args) { return args; }