ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • Effective Java 아이템 32 (Generic, Varargs, Heap Pollution)
    Java, JVM 2021. 8. 23. 22:14

    힙 오염이란?

    힙 오염은 parameterized type의 변수가 자신과 다른 타입의 객체를 참조하는 경우에 발생한다.

    (Parameterized type은 List<String>, Set<Integer> 같은 것을 의미한다.)

     

    다음과 같은 상황을 들 수 있다.

    static method heapPollution() {
    	
        List<String> stringList = new ArrayList<>();
        List<Integer> integerList = List.of(1);
        stringList = (List<String>) (Object) integerList; // 힙 오염 발생
        String s = stringList.get(0); // ClassCastException
    }

    List<String> 타입인 stringList가 List<Integer> 타입의 객체를 참조하게 되면서 힙 오염이 발생하였다. 이 코드는 컴파일은 되지만, 런타임에 마지막 줄에서 ClassCastException이 발생한다.

     

    제네릭과 varargs를 함께 쓸 때 주의할 점

    varargs는 메서드를 사용하는 쪽에서 몇 개의 인자든 마음대로 넘길 수 있게 해주는 기능이다. 그런데 varargs는 제네릭과 함께 사용했을 때 위험한 문제를 일으킬 수 있다. 다음은 책에 나오는 예시이다.

    public class Varargs {
    
        static <T> T[] toArray(T... args) {
        	// 제네릭과 함께 varargs 사용
        	return args;
        }
    	
        static <T> T[] pickTwo(T a, T b, T c) {
        	switch(ThreadLocalRandom.current().nextInt(3)) {
                case 0: return toArray(a, b);
                case 1: return toArray(a, c);
                case 2: return toArray(b, c);
            }
            throw new AssertionError();
        }
        
        public static void main(String[] args) {
        	// toArray에 전달된 인자에 접근하는 코드
        	String[] attributes = pickTwo("a", "b", "c");
        }
        
    }

    얼핏 보면 문제가 없어 보이지만 위 코드는 런타임에 ClassCastException을 발생시킨다. 런타임에 toArray의 리턴 타입은 Object[]인데, main에서 Object[]를 String[]로 형변환을 시도하기 때문이다. 설명하자면 다음과 같은 상황이다.

    // no exception
    Object[] o = new String[10];
    String[] s = (String[]) o;
    
    
    // ClassCastException -> 여기에 해당
    Object[] o = new Object[10];
    String[] s = (String[]) o;

    실제로 힙 오염  발생할 수 있기 때문에 제네릭과 함께 varargs parameter을 사용하면 "Possible heap pollution..."이라는 경고가 출력된다. 만약 문제가 발생하지 않는 코드가 확실하다면 @SafeVarargs를 사용하여 이 경고를 숨길 수 있다. @SafeVarargs는 메소드 사용자에게 경고를 숨기는 역할을 하기 때문에 반드시 실제로 코드가 안전하다는 확신이 있을 때만 사용해야 한다. 다음은 책에 나오는 안전한 코드의 예시이다.

    @SafeVarargs
    static <T> List<T> flatten(List<? extends T>... lists) {
       
        List<T> result = new ArrayList<>();
        for (List<? extends T> list : lists) {
        	result.addAll(list);
        }
        return result;
    }

    (또는 클라이언트에서 아예 varargs를 사용하지 않고 List를 넘기도록 만드는 것도 한 가지 방법이라고 한다.)

     

     

     

     

     

     

    출처: Effective Java, Third Edition

    'Java, JVM' 카테고리의 다른 글

    자바에서 날짜와 시간 다루기  (0) 2022.03.20

    댓글

Designed by Tistory.