JAVA/Effective Java

제네릭 - 규칙 25 배열 대신 리스트를 써라

반응형

배열과 제네릭 자료형(List)의 차이


배열은 convariant  제네릭 자료형은 invariant 자료형이다.

차이 1.

covariant 
- Sub[] 이 Super 의 하위자료형이라면 Sub[] 은 Super[]의 서브 타입이다. 

invariant
Type1 Type2 List<Type1> List<Type2> 의 서브타입도 슈퍼타입도 아니다.
그렇기 때문에 List<Type1>은 List<Type2>의 서브타입 또는 슈퍼타입도 될 수 없다.

이런 이유로 제네릭 쪽이 배열보다 취약한 것 같지만 다음과 같은 문제를 보면 
오히려 문제가 발생하는 부분은 배열이다.



1
2
3
4
5
6
7
8
// 실행 중에 문제를 일으킴 (배열)
Object[] objectArray = new Long[1];
objectArray[0] = "babo ya";
=> 컴파일시 문제는 없지만 런타임 시 ArrayStoreException 예외가 발생한다.
 
// 컴파일시 문제가 발생하는 코드 (제네릭)
List<Object> ol = new ArrayList<Long>(); 
ol.add("babo ya");
cs



배열과 제네릭 모두 Long 객체 컨테이너안에 String 객체를 넣을 수는 없다.
그러나 배열은 위와 같이 실수를 유발할 수 있기 때문에 제네릭 타입이 더 좋다.


차이 2.
배열은 실체화(reification) 되는 자료형이다.
-> 배열의 각 원소의 자료형은 런타임시에 결정된다.
-> 위의 예를 통해 보면 알 수 있지만, String객체를 Long 배열에 넣는것이 컴파일시에는 문제가 발생하지 않는다는것이다.

리스트는 컴파일 시점에만 적용되고, 자료형 정보는 실행될 때는 삭제된다.


이런 차이들로 인해 배열과 제네릭(List)는 섞어서 사용하기 어렵다.
그렇기 때문에 new List<E>[], new List<String>[], new E[]는 전부 컴파일 되지 않는 오류이다.

이로인해 제네릭 배열을 만들 수 없다는 것이 불편함을 야기할 수 있다.
그렇기에 List<E>를 써서 배열을 대신할 수 있다. 대신 불편하지만 형안전성을 보장받을 수 있다.

그리고

배열은 컴파일시 리스트는 런타임시 체크를 하기 때문에
다음과 같이 list의 toArray에 캐스팅을 하게되면 런타임시 E가 무슨 자료형이 될지 알 수 없기 때문에 아래와 같은 경고 메시지가 출력된다.



<E> E reduce(List<E> list) { E[] snapshot = (E[]) list.toArray(); // uncheck cast 경고 메시지 }




이는 제네릭(List)는 불변 자료형이며, 실행시간에 형인자의 정보는 삭제가 되기 때문에 배열로 변경될 시 형 안정성을 보장하지 못하기 때문에 오류가 발생하는 것이다. 

그래서 위와 같은 경우에는 배열이 아닌 ArrayList를 사용해야 한다.



1
2
3
<E> E reduce(List<E> list) {
  List<E> snapshot = new ArrayList<>(list);
}
cs



결론으로
배열과 제네릭은 쉽게 혼용될 수 없다. 
서로 혼용해서 사용하다가 컴파일 오류나 경고 메시지를 만나게 되면, 배열을 리스트로 바꿔서 사용해야 한다는 것을 기억해야 한다.

출처 : 조슈아 블로크, 『 Effective Java 2/E』, 이병준 옮김, 인사이트(2014.9.1), 규칙25 인용.

반응형