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

제네릭과 가변인수를 함께 쓸 때 발생하는 문제

"메서드를 선언할 때 실체화 불가 타입으로 varargs 매개 변수를 선언하면? 컴파일러가 경고를 보낸다."

warning: [unchecked] Possible heap pollution from parameterized vararg type List<String>
  • 실체화 불가 타입: 런타임 시점에 타입 정보가 완전히 남아있지 않는 타입 (Ex. 제네릭 타입(List, …), 매개변수화된 타입(List, …))

  • 이러한 타입들은 타입 소거(type erasure) 과정을 거쳐 런타임에는 구체적인 타입 인자가 소실되어 List와 같은 로(raw) 타입으로 처리됨.

  • 가변 인수: 메서드에 임의의 개수의 인수를 전달할 수 있도록 하는 기능. 내부적으로는 이 인수들을 담기 위한 배열이 자동으로 생성됨.

  • 예시) List<String>... lists → 내부적으로 List<String>[] 배열로 변환됨.

What If…메서드를 선언할 때 실체화 불가 타입으로 varargs 매개변수를 선언하면? (Ex. List<String>으로 varags 매개변수 선언)

  1. 자바에서는 제네릭 배열(List<String>[])을 직접 생성할 수 없음.

  • new List<String>[10]; 같은 코드를 작성하면 컴파일 오류 발생 → 타입 소거(Type Erasure) 때문에 런타임에는 로 타입으로 처리됨.

  1. 실체화 불가 타입으로 varargs 매개변수를 선언하면 (List<String>... lists) → 내부적으로 List<String>[] 배열이 생성됨.

  • 자바는 제네릭 배열을 금지함. 하지만 varargs를 사용하면 내부적으로 제네릭 배열이 생성될 수 있음.

  1. 컴파일러는 런타임 시 배열의 정확한 요소 타입을 알 수 없음.

  • 가변 인자(...)는 내부적으로 배열을 사용하므로, 타입 정보가 소거된 상태에서 배열을 다루게 됨.

  1. 제네릭 배열이 Object[]처럼 동작하여, 다른 타입의 객체를 저장할 가능성이 있음.

  • 따라서, 타입 안전성 문제가 발생할 가능성을 인지하고 경고를 발생시킴.


"가변인수 메서드를 호출할 때도 varargs 매개 변수가 실체화 불가 타입으로 추론되면? 그 호출에 대해서도 경고를 낸다."

  • T... args 형태의 가변인수 메서드를 호출할 때, 때로는 전달되는 인수 Hello, World의 타입 String에 기반하여 varags 매개변수의 제네릭 타입 TString으로 추론될 수 있음.

  • 만약 추론된 타입이 실체화 불가 타입이라면? → 만약 TList<String>으로 추론된다면?

  • 런타임 시 타입 안전성 문제가 발생할 수 있음 → 내부적으로 T... argsList<String>[]처럼 동작함.

  • 따라서 컴파일러는 이러한 호출에 대해서도 힙 오염이 발생될 수 있다는 경고를 발생시킴.

코드 예시 1 (가변 인수(varargs)와 제네릭 타입 추론)

자바에서는 메서드를 호출할 때 전달된 인수의 타입을 기반으로 제네릭 타입을 추론함.

  • 위 코드에서는 T의 타입이 String 또는 Integer로 추론되며, 타입이 확실하기 때문에 문제가 없음.

코드 예시 2 (실체화 불가 타입(non-reifiable type) 문제)

제네릭 타입(List<String>, List<Integer>)을 가변 인자로 사용하면 문제가 발생할 수 있음.

  • 이 과정 자체는 문제가 없어 보이지만, 제네릭 타입이 가변 인자로 사용될 때는 Heap Pollution(힙 오염)이 발생할 위험이 있음.


"매개변수화 타입의 변수가 타입이 다른 객체를 참조하면? 힙 오염이 발생한다."

  • 힙 오염: 제네릭 타입 시스템의 타입 안전성이 런타임 시에 깨지는 현상.

  • 매개변수화된 타입(예: List<String>)으로 선언된 변수가 컴파일 타임에 명시된 타입 인자와 다른 타입의 객체를 참조하게 될 때 발생.

힙 오염은 주로 다음과 같은 상황에서 발생할 수 있음.

  1. 로 타입(raw type): 제네릭 타입을 사용할 때 타입 인자를 생략하면 로 타입(raw type)으로 간주.

  2. 로 타입(raw type)의 변수는 어떤 타입의 객체든 참조할 수 있음.

  3. 매개변수화된 타입의 변수가 로 타입(raw type) 변수를 통해 다른 타입의 객체를 참조하게 되면 힙 오염이 발생할 수 있음.

코드 예시 (로 타입을 통해 힙 오염이 발생하는 경우)


“가변인자로 제네릭 타입을 사용한다면? 타입 안전성 문제가 발생할 수 있음”



컴파일 경고 무시해도, 힙 오염은 유발하지 않을 자신있어! @SafeVarargs 애너테이션

  • @SafeVarargs(자바 7 도입)는 제네릭 가변인자 메서드에서 발생하는 컴파일 경고를 억제하는 애너테이션이다.

  • [unchecked] Possible heap pollution from parameterized vararg type List

  • 메서드 작성자가 타입 안전성을 직접 보장해야 하며, 안전하지 않은 경우 힙 오염(Heap Pollution)과 ClassCastException을 유발할 수 있음.

타입 안전성을 위한 조건 (1) - 가변 인수 배열을 수정하지 않기

  • 제네릭 가변 인자 메서드는 내부적으로 배열을 사용하므로, 배열의 요소를 수정하면 힙 오염(Heap Pollution)이 발생할 수 있음.

  • 특히, 가변 인자 배열(List... lists)에 다른 타입의 값을 할당하거나 덮어쓰면 타입 안정성이 깨짐.

  • 이로 인해 런타임에 ClassCastException과 같은 예외가 발생할 위험이 있음.

코드 예시


타입 안전성을 위한 조건 (2) - 가변 인자 배열의 참조를 외부로 노출하지 않기

  • 가변 인자로 받은 배열을 외부로 반환하면 안됨.

  • 이 배열이 외부에서 수정되면, 다른 타입의 값이 들어갈 수 있음.

  • 잘못된 타입이 저장되면, ClassCastException 같은 오류가 날 수 있음.

  • 컴파일 시에는 문제없지만, 실행 중 오류가 터짐.

코드 예시

  • toArray가 어떻게 동작하는지 확인해보자.

  • 이 코드의 문제는 제네릭 배열(T[])이 내부적으로 Object[]처럼 동작할 수 있다는 점!


발표 자료

https://byumm315.atlassian.net/wiki/external/NmZjYjg3OWVhYWE5NGZmNzk4NDUyOWJjZTIxNjM2MDQ

Last updated