본문 바로가기
PROGRAM/effective JAVA

Generic - 배열보다는 List를 사용하자.

by ojava 2011. 5. 30.
반응형

이 카테고리는 ‘effective JAVA’ 라는 책을 공부하고 포스팅하는 공간입니다.

 

 

 

오늘의 주제는 배열보다는 List를 사용하자! 는 내용입니다.

이는 최근 effective JAVA 카테고리에 올렸던 ‘Generic을 사용하자라는
전체적인 주제와 같은 목적을 가진다고 보아도 무방합니다.

 

우선 배열이 List로 대변되는 Generic과 어떤 점에서 다른 지를 살펴보러 갑시다!

 

 

1. 배열은 공변(covariant)이고, Generic은 불변이다.

 

공변이라는 단어가 매우 생소합니다. 그래서 공변에 대한 설명도 붙여 넣었으니 참조하시면 좋을 듯 하네요.


프로그램적으로 더 와 닿을 얘기를 예로 들자면, 배열이 공변이라는 얘기는
Sub
라는 클래스가 늘 Super라는 클래스의 서브클래스(자식클래스)라면
이를 통해 정의한 Sub[] 배열 역시 Super[] 배열의 자식이라고 보는 내용을 말합니다.

 

반면 Generic은 불변(invariant)합니다. 하나의 내용이 변한다고 해서 다른 내용도 함께 변하는 것이 아니라는 얘기지용.
이 내용에 대해서 공변과 비교하자면 만약 서로 다른 Type1Type2가 존재하는데
불변타입인 GenericList<Type1> List<Type2>에 대해서 아무런 관련이 없다는 것을 의미합니다.

 

이 둘의 사용에서 가장 큰 차이점은, 배열을 사용하여 에러가 발생하는 경우는 런타임 시 그 내용을 알 수 있고
Generic
을 사용하여 에러가 발생하는 경우는 컴파일 시 내용을 알 수 있다는 점입니다.

이는 앞에서 설명했던 Generic의 사용 이유이고,
다시 한 번 설명하자면 런타임보다 컴파일이 더 빠른 시점이므로
Generic
사용으로 더 빨리 에러를 알아낼 수 있으며 컴파일러의 도움을 받아 해결이 가능해집니다.

 

 


 

2. 배열은 구체적이고, Generic은 비구체적이다.

 

어딜 봐도 위의 문장은 배열이 좀 더 좋지 않나? 라고 생각하게 되지만 실은 아닙니다.

여기에서 구체적이라는 의미는 자세하고 상세하다는 의미로서 사용되는 것이 아니라,
배열은 정제되어 있다는 뜻으로 자신의 요소 타입을 런타임 시에 알고 지키게 된다는 것을 의미합니다.


반대로 Generic은 소거자(erasure)에 의해 구현됩니다. 이는 구체적이지 않다는 뜻으로, 간단하게 비 구체적이라고 표현하였으며
자신의 요소 타입을 컴파일 시에만 알고 있어 그 타입에 대해서 지키게 하며,
런타임 시에는 소거자에 의해서 자신의 요소 타입을 잊어버리게 된다는 뜻입니다.

여기서 소거자는, 제네릭을 사용하지 않는 기존 코드가 제네릭과 상호 운용될 수 있도록 해주는 것입니다.
 
원천 타입이 1.5 버전 이후에도 존재하는 것과 같은 이유로 존재하고 있습니다.

Generic은 런타임 시에 자신에 대한 정보를 가지지 않지만 오류가 발생하지 않는 것은,
런타임보다 앞선 컴파일 단계에서 자신과 맞지 않는 타입을 받아들이지 않기 때문입니다.

 

배열타입과 Generic타입의 에러발생의 예시를 책에서 인용해왔으니 참고하시길 바랍니다.

 

[1] 런타임 시에 에러 발생

Object [] objectArray = new Long [1];

objectArray[0] = “문자열"; //ArrayStoreException 예외 발생

[2] 컴파일 시에 에러 발생

List<Object> objectList = new ArrayList<Long>(); //호환이 안 되는 타입

objectList.add(“문자열");

 

 

두 타입의 차이점에 대해서 다시 한번 정리하자면,
구체화와 비구체화의 차이는 컴파일 시보다 런타임 시에 어떤 것이 더 많은 정보를 가지느냐는 문제입니다.
따라서 런타임 시 더 많은 정보를 가지는 배열이 구체적,
컴파일 시 더 많은 정보를 가지는 Generic은 비구체적이라고 하는 것이지요.

 

 


 

배열과 Generic은 위의 두 가지의 차이점을 가지기 때문에 혼용해서 사용되지 않습니다.

new List<E>[], new List<String>[] 과 같이 둘을 혼용한 것을 제네릭 배열이라고 부르며,
이런 선언을 하게 되면 제네릭 배열 생성 에러가 발생하게 됩니다.
 

생성되지 않는 이유는 배열은 런타임시 타입안전을 보장하고 컴파일시에는 보장하지 않지만
제네릭타입은 컴파일시 타입안전을 보장하고 런타임시에 보장하지 않는 정 반대의 성질을 지녔기 때문에,
이러한 타입 안전상의 문제 때문에 제네릭과 배열을 결합한 형태를 사용하지 않습니다.


그러나 언바운드 와일드 카드 타입 (<?>) 은 제네릭 형태중에서도 구체화 타입이므로 배열 생성이 가능합니다.





 

하지만 언바운드 와일드 카드 타입이 아닌
일반적인 비구체화 타입을 이용해서 제네릭 배열을 생성해야 하는 일이 생길 수 있습니다.
만약 제네릭 배열을 생성해야 하는 일이 온다면 
책에 주어진 제네릭 배열을 생성하는 예제를 보면서 진행하겠습니다.

 

Static <E> E reduce(List<E> list, Function<E> f, E initVal){

          E[] snapshot = list.toArray();

           E reslut = initVal;

           for(E e : snapshot)

                     result = f.apply(result.e);

           return result;

}

 

항목 27에서 소개할 제네릭 메소드가 나왔네요.
이 메소드의 내용은 차후에 다루게 될 것이니 메소드 구조보다는 안의 snapshot 부분에 주목해주시면 됩니다.


이 메소드는 원천타입인 Object의 배열에서
제네릭 매개변수 타입인 E 배열로 변경한 코드이지만 컴파일 시 에러가 발생합니다.
bold
처리한 줄에서 에러가 발생합니다.

그래서 아래와 같이 E 배열로 캐스팅을 하면 어떻게 될까요?

 

Static <E> E reduce(List<E> list, Function<E> f, E initVal){

E[] snapshot =(E[]) list.toArray();

           E reslut = initVal;

           for(E e : snapshot)

                     result = f.apply(result.e);

           return result;

}

 

이번에는 컴파일은 됩니다. 하지만 항목 24에서 지적했던 경고 메시지가 발생하네요.

unchecked 경고가 나오면서, 런타임 시 해당 cast에 대한 안전을 검사할 수 없다는 내용입니다.
그 이유는 컴파일러가 위의 코드를 통해서 E가 어떤 타입이 되어야 할지에 대한 추론을 할 수 없지 때문입니다.
 
컴파일이 완료되었고 제네릭 타입이므로 런타임 시 타입정보가 없어지므로 이 코드는 작동할 것입니다.
 
하지만 타입안전이 검증되지 않은 상태의 코드를 사용한다는 것은 조금 위험성이 따른다고 생각되므로
한 번의 코드 수정을 더 거치겠습니당.

드디어 오늘의 핵심 내용인 배열 대신에 List를 사용해봅시다.

 

Static <E> E reduce(List<E> list, Function<E> f, E initVal){

List <E> E snapshot = new ArrayList<E>(list);

           E reslut = initVal;

           for(E e : snapshot)

                     result = f.apply(result.e);

           return result;

}

 

E [] 대신 컬렉션 타입인 List<E>를 사용한 위의 코드는, 앞의 내용보다 복잡해 보입니다.
그렇기에 코드의 간결함 관점이나 성능적인 면에서는 일부분 손해를 봅니다.

하지만 cast 타입에 대한 경고 메시지를 발생시키지 않는 List로 작성되어 있으므로
컴파일 에러와 경고 메시지에 대해서 안전한 타입 안전을 제공하고,
또한 제네릭 이전의 기존 코드와의 상호 운용성의 측면에서는 더욱 좋아지므로 List를 사용하고 있습니다.



 
날이 더워졌어요. 그래도 저녁에는 쌀쌀하니 얇게라도 걸칠 옷 가지고 다니세요~




2011.05 오혜영 작성 

반응형