■ 배열과 제네릭 차이점

1. 배열은 공변이다. Sub가 Super의 하위 타입이라면 배열 Sub[]는 배열 Super[]의 하위 타입이다. 즉, 함께 변한다는 뜻이다. 반면에 제네릭은 불공변이다. List<Type1>은 List<Type2>의 하위 타입도 아니고 상위 타입도 아니다. 제네릭에 문제가 있다고 생각할 수 있지만 사실 문제가 있는건 배열이다.

 

리스트를 사용할 경우 컴파일 때 에러를 바로 알 수 있다.

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

// 다음 코드는 문법에 맞지 않아 컴파일조차 되지 않음
List<Object> ol = new ArrayList<Long>(); // 호환되지 않는 타입이다
ol.add("타입이 달라 넣을 수 없다.");

 

2. 배열은 실체화(reify)된다. 즉, 배열은 런타임에도 자신이 담기로 한 원소의 타입을 인지하고 확인한다. Long 배열에 String을 넣으려고하면 ArrayStoreException이 발생한다. 반면에 제네릭은 타입 정보가 런타임에 소거된다. 따라서 런타임시에는 어떠한 타입도 들어올 수 있다. 하지만 컴파일 타임에서 이를 걸러내기 때문에 안전한다. 원소 타입을 컴파일타임에만 검사하며 런타임에는 알수조차 없다는 뜻이다. 

 

이러한 차이점으로 배열과 제네릭은 잘 어우러지지 못한다. 배열은 제네릭 타입, 매개변수화 타입, 타입 매개변수로 사용할 수 없다.

 

■ 제네릭 배열을 만들지 못하게 막은 이유

타입이 안전하지 않기 때문이다. 만약 허용한다면 컴파일러가 자동 생성한 형 변환 코드에서 런타임에 ClassCastException이 발생할 수 있다. 하지만 이건 제네릭 타입 시스템의 취지에 어긋난다.

 

■ 실체화 불가 타입

E, List<E>, List<String> 같은 타입을 실체화 불가 타입이라 한다. 쉽게 말해, 실체화되지 않아서 런 타임에는 컴파일 타임보다 타입 정보를 적게 가지는 타입이다. 매개변수화 타입 가운데 실체화될 수 있는 타입은 List<?>와 Map<?,?> 같은 비한정적 와일드카드 타입뿐이다.

 

// 컬랙션 안의 원소 중 하나를 무작위로 선택해 반환
// 이 클래스를 사용하면 choose 메서드를 호출할 때마다 반환된 Object를 원하는 타입으로 형변환해야 한다
// 제네릭을 시급히 적용해야 한다
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)];
	}
}
// 리스트 기반 Chooser - 타입 안전성 확보! (168쪽)
// 코드 양이 조금 늘었지만, 런타임에 ClassCastException을 만날 일은 없으니 가치가 있음
public class Chooser<T> {
    private final List<T> choiceList;

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

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

    public static void main(String[] args) {
        List<Integer> intList = List.of(1, 2, 3, 4, 5, 6);

        Chooser<Integer> chooser = new Chooser<>(intList);

        for (int i = 0; i < 10; i++) {
            Number choice = chooser.choose();
            System.out.println(choice);
        }
    }
}

 

* 참고할만한 포스팅 : 제네릭의 형변환

  • 네이버 블러그 공유하기
  • 네이버 밴드에 공유하기
  • 페이스북 공유하기
  • 카카오스토리 공유하기