brunch

You can make anything
by writing

C.S.Lewis

by 이승현 Jan 21. 2019

Effective Java3/E - 제네릭

#32 제네릭과 가변 인수를 함께 쓸 때는 신중하라

Effective Java 3/E - 제네릭


#32 제네릭과 가변 인수를 함께 쓸 때는 신중하라


#01 generic & varargs


"제네릭과 가변 인수를 함께 쓸 때는 신중하라"는 결국 "제네릭과 가변 인수(배열)를 함께 쓸 때는 신중하라"라는 의미를 나타냅니다.


위 코드에서 보듯이, 자바에서는 기본적으로 제네릭 배열(T[]) 직접 생성을 허용하지 않습니다.

Super 클래스 - Sub 클래스 (상속 관계)
Super[] - Sub[] (상속 관계) // 공변
List<Super> - List<Sub> (아무 관계 아님) // 불공변

////////////////////////////////////////////////////////////////////////////////////////////////

Object[] objects = new Long[1];
objects[0] = "g"; // 런타임 에러(ArrayStoreException)  발생, 구체적(reified)

List[] objectList = new ArrayList<Long>();
objectList.add("g"); // 컴파일 에러 발생, 비구체적

////////////////////////////////////////////////////////////////////////////////////////////////

T[] newArray = new T[SIZE]; // 타입이 안전하지 않기 때문에 직접 제네릭 배열 생성 허용 X
** 구체적(reified) : 런타임 시에 자신의 요소 타입을 체크한다는 의미입니다.


하지만 가변 인수를 통해 제네릭 배열을 이용할 수 있습니다.

static <T> void gv(T... args) { // 가변 인수 args를 담기 위한 배열이 자동으로 만들어짐
   T[] varargsArray = args; // ok
}


가변 인수를 통해 제네릭 배열을 이용하면 어떠한 문제가 발생하는지에 대해 알아보겠습니다.




#01 Heap pollution - 가변 인수 매개변수 배열에 저장


#02 Heap pollution


제네릭과 가변 인수를 같이 쓰면 힙 오염(Heap pollution)이 발생할 수 있습니다.

** 힙 오염(Heap pollution) : 매개변수화 타입의 변수가 타입이 다른 객체를 참조하는 경우 발생.
왜 힙 오염이라 표현하는지 찾아보려 했는데, 못 찾겠네요ㅠ
배열이 힙 영역에 저장되고, 의도와 달리 오염? 되기 때문에 힙 오염이라 표현하지 않나 추측합니다.


위 코드처럼 런타임 시 예외가 발생할 수 있기 때문에, 가변 인수 매개변수 배열에 값을 저장하는 것은 안전하지 않습니다.


#03 Warning heap pollution




#02 Heap pollution - 가변 인수 매개변수 배열을 노출

#04 Heap pollution


런타임 시 예외가 발생할 수 있기 때문에, 가변 인수 매개변수 배열을 그대로 노출하는 것도 안전하지 않습니다.




#03 제네릭 or 매개변수화 타입의 가변 인수


제네릭 배열을 직접 생성은 못하게 하면서,
제네릭 가변 인수 매개변수를 통한 생성을 허용한 이유는?


실무에서 유용해서..

힙 오염이 발생할 수 있고, 런타임 시 예외가 발생할 수 있는데도 가변 인수를 통해 제네릭 배열을 생성하게 한 이유는 책에 따르면, 실무에서 유용해서 라고 하네요.

이용하는 개발자들이 주의해야 합니다.


실제 자바 라이브러리에서도 이런 메서드를 여럿 제공하고 있습니다.


#05 Arrays.asList(T... a)

Arrays.asList(T... a)

Collections.addAll(Collections<? super T> c, T... elements)

EnumSet.of(E first, E... rest)




#04 @SafeVarargs


자바 라이브러리에서 제공하는 제네릭 타입의 가변 인수는 타입 안정성을 보장하고 있습니다.

이런 경우에는 @SafeVarargs annotation을 메서드에 추가하면, 더 이상 이 메서드를 호출하는 부분에서 힙 오염 경고를 표시하지 않습니다.


반대로 말하면 타입 안정성을 보장할 수 없다면, @SafeVarargs를 써서는 안 됩니다.


타입 안정성을 보장하기 위해서는 몇 가지 조건이 필요합니다.

제네릭 가변 인수로 인해 생성된 배열에 아무것도 저장하지 않는다.

생성된 배열(or 복제본)의 참조가 밖으로 노출되지 않는다.

제네릭 가변 인수로 순수하게 인수들을 전달받기만 한다.

@SafeVarargs
public static <T> List<T> asList(T... a) {
     // 제네릭 가변 인수로 생성된 배열(T[] ts = a;)에 아무것도 저장하지 않는다.
     // 생성된 배열의 참조 아닌, new ArrayList<>(a)가 밖으로 노출된다.
     // 제네릭 가변 인수로 순수하게 인수들을 전달받기만 한다.
     return new ArrayList<>(a);
}




#05 List 이용


반드시 배열을 써야 할 상황이 아니라면 List를 이용할 수 있습니다.

위에서 봤던 toArray(T... args) 메서드 대신, 자바 라이브러리 차원에서 제공하는 List.of(E... elements)를 이용할 수도 있습니다.


#06 List




https://docs.oracle.com/javase/tutorial/java/generics/nonReifiableVarargsType.html

http://www.angelikalanger.com/GenericsFAQ/FAQSections/ProgrammingIdioms.html#FAQ300


힙 오염 관련 내용이 많지 않네요.

제네릭 가변 인수를 쓰는 경우가 많진 않지만, 쓴다면 고민할 부분이 있는 거 같습니다.

매거진의 이전글 Effective Java3/E - 클래스와 인터페이스
브런치는 최신 브라우저에 최적화 되어있습니다. IE chrome safari