이펙티브 자바, 쉽게 정리하기 - item 28. 배열보다는 리스트를 사용하라
공변(covariant)과 불공변(invariant) 개념
공변
: 함께 변한다.- 배열은
공변
이 적용된다.
- 배열은
불공변
: 함께 변하지 않는다. (공변이 아니다.)- 리스트는
불공변
이 적용된다.
- 리스트는
공변(covariant) 불공변(invariant) 예제 테스트 코드
@Test
public void covariantTest() {
Object[] objects = new Long[1];
objects[0] = "String"; // ArrayStoreException
}
Object[]
의 공변 특성을 이용하여objects
를 선언 후에Long[]
저장소를 만들어 할당했다.- 해당 저장소 타입이
Object[]
로 정의되어 있어 클라이언트는 아무 타입이나 된다는 가정 하에 문자열을 넣은 경우이다.- 런타임에
ArrayStoreException
예외가 발생한다.
- 런타임에
@Test
public void invariantTest() {
List<Object> ol = new ArrayList<Long>(); // 불공변이라 타입 자체가 호환이 안된다. 이미 에러가 뜨고 있다.
ol.add("아아");
// 제네릭의 타입 정보가 런타임에는 이미 사라져버리기 때문에 컴파일 타임에 검사한다.
// 런타임에 제네릭 타입을 소거시키는 이유는 레거시 코드와 제네릭 타입을 함께 이용할 수 있게 해주기 위해서이다.
}
- 불공변 특성을 가지는 리스트에서는 하위 타입과 같은 관계 자체가 존재하지 않고, 모든 관계가 그저 다른 타입일 뿐이므로 에러가 발생한다.
- 이미 IDE에서 에러가 잡히기 때문에, 런타임에 심각한 에러로 발전할 가능성이 없다.
- 당연히 에러는 컴파일 타임에 잡는 것이 훨씬 이상적이다.
배열은 실체화(reify)된다.
- 배열이 실체화된다는 것은 자신이 담기로 한 원소의 타입을 인지하고 확인한다는 뜻이다.
- 제네릭은 실체화되지 않는다. 타입 정보가 런타임에는 소거된다.
- 이러한 차이로 제네릭과 배열은 어울릴 수 없다.
- 이를테면 배열은 제네릭 타입, 매개변수화 타입, 타입 매개변수로 사용할 수 없다.
- 코드를
new List<E>[], new List<String>[], new E[]
식으로 작성하면 컴파일할 때 제네릭 배열 생성 오류를 일으킨다.
- 코드를
@Test
public void genericArrayTest() {
List<String>[] stringLists = new List<String>[1]; // IDE 내 generic array creation 컴파일 에러
List<Integer> intList = List.of(42);
Object[] objects = stringLists;
objects[0] = intList;
String s = stringLists[0].get(0); // * 문제가 되는 부분
}
- 위의 코드에서 만일
generic array creation
컴파일 에러가 동작하지 않는다면, 컴파일 에러가 전혀 나지 않을 것이라는게 문제다.- 결국 런타임에 다다라서
String s = stringLists[0].get(0)
에서ClassCastException
이 뜨긴 할 것이다.
- 결국 런타임에 다다라서
제네릭은 실체화(reify)되지 않는다.
E
,List<E>
,List<String>
같은 타입은 실체화 불가(reify) 타입이라고 한다.- 런타임에 컴파일타임보다 타입 정보를 적게 가지는 타입이다.
- 소거 매커니즘 때문에, 매개변수화 타입 가운데 실체화될 수 있는 타입은
List<?>
와Map<?, ?>
같은 비한정적 와일드카드 타입 뿐이다.- 배열을 비한정적 와일드카드 타입으로도 만들 수는 있지만 크게 쓸모는 없다.
이러한 이유로 배열과 함께 쓰여서도 안되고, 가변인수 메서드와도 함께 쓰일 때도 배열의 원소가 실체화 불가 타입이라면 경고가 발생한다.
후자의 경우@SafeVarargs
애너테이션으로 대처할 수 있다.
배열 형변환 시 에러 해결하기
- 배열로 형변환하는 경우, 제네릭 뱅려 생성 오류나 비검사 형변환 경고가 뜨는 경우 대부분 배열인
E[]
대신 컬렉션인List<E>
를 사용하면 해결된다.- 코드는 조금 복잡해지고 성능이 안좋아질 수 있지만, 타입 안정성과 상호 운용성이 좋아진다.
제네릭으로 코드 개선해보기
제네릭을 사용하지 않는 Chooser
클래스
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
를 원하는 타입으로 형변환해야 한다.- 공변(covariant)이 적용되어, 혹여나 타입이 다른 원소가 들어있었다면, 런타임에
ClassCastException
이 났을 것이다.
제네릭으로 만들기 위한 첫 시도
public class Chooser<T> {
private final T[] choiceArray;
public Chooser(Collection<T> choices) {
choiceArray = choices.toArray();
}
public Object choose() {
Random rnd = ThreadLocalRandom.current();
return choiceArray[rnd.nextInt(choiceArray.length)];
}
}
- 위 경우 한가지 에러가 뜰텐데, 바로
incompatible types: Object[] cannot be converted to T[]
와 같은 메세지가 뜰 것이다. collection.toArray()
는Object
타입을 반환하도록 되어있기 때문에(T[])
로 캐스팅을 해주어야 한다.choiceArray = (T[]) choices.toArray()
여야 한다.- 다 고친 이후에도
unchecked cast
경고가 뜰 것이다. 그 이유는 런타임에는 제네릭 타입 정보가 전부 소거되었기 때문에 이게 안전한 캐스팅인지 런타임에 알 수 없기 때문이다.
배열보다는 List
를 적극 활용하여 완성시키자
public class Chooser<T> {
private final List<T> choiceList;
public Chooser(Collection<T> choices) {
choiceList = new ArrayList<T>(choices);
}
public Object choose() {
Random rnd = ThreadLocalRandom.current();
return choiceList.get(rnd.nextInt(choiceArray.length));
}
}
- 동작은 정확히 같은 동작을 한다.
- 다만 배열을 빼고 제네릭을 활용하는 리스트를 사용함으로써, 잠재적인 에러를 모두 잡았다.
- 공변은 불공변보다 약간 위험하다는 사실을 깨달았다.
정리
- 배열은 공변이고 실체화된다는 점에서 잘못 사용했을 때 불안정한 코드가 나온다.
- 제네릭은 불공변이고 타입 정보가 소거된다.
- 결과적으로 배열은 런타임에 에러 가능성이 존재하고, 제네릭은 그렇지 않다.
- 그래서 둘을 섞어 쓰긴 어렵다.
- 둘을 섞어 쓰다가 컴파일 오류를 만나면, 과연 이 작업에 진짜 배열이 필요한지 생각해보고 아니라면 바로 제네릭을 사용하는 리스트로 변경하자.
반응형
'Java > 이펙티브 자바' 카테고리의 다른 글
이펙티브 자바, 쉽게 정리하기 - item 30. 이왕이면 제네릭 메서드로 만들라 (0) | 2022.01.09 |
---|---|
이펙티브 자바, 쉽게 정리하기 - item 29. 이왕이면 제네릭 타입으로 만들라 (0) | 2022.01.09 |
이펙티브 자바, 쉽게 정리하기 - item 27. 비검사 경고를 제거하라 (0) | 2022.01.09 |
이펙티브 자바, 쉽게 정리하기 - item 26. 로 타입은 사용하지 말라 (0) | 2022.01.09 |
이펙티브 자바, 쉽게 정리하기 - item 25. 톱 레벨 클래스는 한 파일에 하나만 담으라 (0) | 2022.01.09 |