배열과 제네릭타입의 차이는 다음과 같다.

  • Sub가 Super의 하위 타입이라면 Sub[]는 배열 Super[]의 하위타입이다(공변성)
  • 서로다른 타입 Type1 Type2가 있을때 List<Type1> List<Type2>는 연관성이 없다.

 

이런 경우에 배열에 문제가 생기는데

Object[] objectArray = new Long[1];
objectArray[0] = "타입이 달라 넣을 수 없다." // ArrayStoreException 발생

이 배열 코드는 문법상 오류는 없지만 런타임시에 오류가 난다.

List<Object> ol = new ArrayList<Long>(); // 호환되지 않는 타입이다.
ol.add("타입이 달라 넣을 수 없다.")

이 코드는 컴파일시에 오류를 잡아줘서 문법상 오류를 바로 잡아준다.

개발자는 컴파일 시 오류를 잡아주는 것을 더 선호할 것이다.

두 번째 주요한 차이로, 배열은 실체화 된다는 점이다.

 

  • 배열은 런타임에도 자신이 담기로 한 원소의 타입을 인지하고 확인한다.
  • 반면 제네릭은 타입 정보가 런타임에는 소거된다.(컴파일 타임에만 원소 검사)

 

이러한 차이 떄문에 제네릭 배열을 만들지 못하게 하는데 예제를 확인해보자.

List<String>[] stringLists = new List<String>[1]; //(1)
List<Integer> intList = List.of(42); //(2) 
Object[] objects = stringLists; //(3)
objects[0] = intList; //(4)
strings = stingList[0].get(0); //(5)

1번의 경우를 허용해 준다고 해보자

2번에서는 크기가 1인 intList를 생성할 것이다.

3번에서는 1번의 배열을 Object 배열에 할당한다 -- 여기까지는 문제가 생기지 않는다( 공변 )

4번에서는 2번에서 생성한 List<Integer> 의 인스턴스를 Object 배열의 첫 원소로저장한다.

제네릭은 소거방식이라 이 방법 또한 성공한다.

--> 런타임시에 List<Integer 가 단순 List로 변한다.

 

문제는 stringList는 List<String> 만 저장하겠다고 했는데 List<Integer>가 저장되어있다.

그래서 5번을 실행하면 ClassCastException을 던진다.

이런 일을 미연에 방지하기 위에서 1번 단계에서 오류를 내야하는 것이다.

 

소거 메커니즘 때문에 E, List<E>, List<String> 같은 타임은 실체화 불가 타입이라 한다.

위와 같은 사례때문에 배열과 제네릭을 함께 사용하기가 여간 까다로운것이 아니다.

 

다음 예시를 통해 배열과 제네릭이 얼마나 까다롭게 작용하는지 확인해보자.

다음 클래스는 컬렉션 안의 원소 중 하나를 무작위로 선택에 반환하는 choose 메소드를 제공한다.

public class Chooser{
    private final Object[] choiceArray;

    public Chooser(Collection choices){
        choiceArray = choices.toArray();
    }

    public Object choose(){
        Random rnd = ThreadLocalRandom.current();
        return choiceArray[rnd.nextInt(choiceArray.length)];
    }
}

이 클래스를 사용하려면 choose 메소드를 호출 할 때 마다 반환된 Object를 원하는 타입으로 형변환해야 한다.

다른 원소가 들어가면 런타임에 형변환 오류가 날 것이다.

위의 코드를 제네릭으로 바꿔보자.

public class Chooser<T>{
    private final List<T> choiceArray;

    public Chooser(Collection<T> choices){
        choiceArray = new ArrayList<>(choices)
    }

    public Object choose(){
        Random rnd = ThreadLocalRandom.current();
        return choiceArray.get(rnd.nextInt(choiceList.size()));
    }
}

이렇게 만들면 런타임에 ClassCastException을 만나지 않게 된다.

+ Recent posts