본문 바로가기
책/effective java

[effective java] item 28. 배열보다는 리스트를 사용하라

by 2245 2023. 8. 28.

결론

배열과 제네릭은 매우 다른 타입 규칙이 적용됩니다.
배열은 공변인 반면, 제네릭은 불공변이고, 배열은 실체화되는 반면, 제네릭은 타입 정보가 소거됩니다. 
그 결과 배열은 컴파일 타임에 안전하고 런타임에는 타입 안전하지 않습니다. 제네릭은 반대입니다.
그래서 둘을 섞어 쓰기란 쉽지 않으므로 만약 둘을 섞어 쓰다가 컴파일 오류나 경고를 만나면, 가장 먼저 배열을 리스트로 대체하는 방법을 적용해봅시다. 

 

 

설명

배열과 제네릭의 차이점

배열과 제네릭에는 다음 두 가지 주요한 차이가 있습니다. 

 

1. 공변(convariant)

배열 제네릭
배열은 공변입니다. 제네릭은 불공변입니다. 
Sub가 Super의 하위 타입이라면,
배열 Sub[]도 배열 Super[]의 하위 타입입니다.
(공변, 즉 함께 변한다는 뜻입니다.)
서로 다른 타입 Type1과 Type2가 있을 때,
List은 List의 하위 타입도 아니고 상위 타입도 아닙니다.

[예제]

배열: Long[]은 Object[]의 하위 타입이다.  -> 런타임 에러

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

리스트: List<Long>은 List<Object>의 하위 타입이 아니다. -> 컴파일 에러

List<Object> ol = new ArrayList<Long>();	//컴파일 에러 발생
ol.add("타입이 달라 넣을 수 없습니다.");

 

둘 모두 Long용 저장소에 String을 넣을 수 없다는 점은 동일하지만, 배열에서는 그 실수를 런타임에서야 알게 되는 반면, 리스트를 사용하면 컴파일 때 바로 알 수 있습니다.

 

 

2. 실체화(reify)

  • 배열은 런타임 시에도 타입을 확인합니다. 
    • 따라서, 위의 예제 코드에서 Long 배열의 0번째 인덱스에 String을 넣으려고 하니 ArrayStoreException 런타임에러가 발생했습니다.
  • 제네릭은 타입 정보가 런타임 시 소거됩니다. 즉, 원소 타입은 컴파일타임에만 검사하며 런타임에는 알 수조차 없습니다. 

 

배열과 제네릭은 이상의 주요 차이로 인해 잘 어우러지지 못합니다.

즉, 배열은 제네릭 타입, 매개변수화 타입, 타입 매개변수로 사용할 수 업습니다.

ex) new List<E>[], new List<String>[], new E[]

 

이렇게 제네릭 배열을 만들지 못하게 막은 이유는 타입 안전하지 않기 때문입니다. 

이를 허용하면, 컴파일러가 자동 생성한 형변환 코드에서 런타임에 ClassCastException이 발생할 수 있습니다.

 

배열의 타입 안전성 테스트

//다음을 허용한다고 가정해봅시다.
List<String>[] stringLists = new List<String>[1];

//원소 하나가 담긴 Integer 리스트 생성
List<Integer> intList = List.of(42);

//Object 배열에 List<String>[] 리스트를 할당 (배열은 공변이므로 할당할 수 있습니다.)
Object[] objects = stringLists;

//objects의 첫 번째 배열의 위치에 Integer 리스트를 저장합니다. (제네릭은 소거 방식으로 구현되어 있기 때문에 이 역시 성공합니다.)
objects[0] = intList;

//문제 발생! List<String> 인스턴스 배열에 List<Integer> 인스턴스 저장 - ClassCastException 런타임 에러 발생
String s = stringLists[0].get(0);

//이런 문제를 발생하려면, 애초에 List<String>[] 배열이 생성되지 않도록 컴파일 오류를 내야 합니다.

 

 

해결책 : 제네릭으로 변경

배열로 형변환할 때, 제네릭 배열 생성 오류나 비검사 형변환 경고가 뜨는 경우 대부분은 배열인 E[] 대신 컬렉션인 List<E>를 사용하면 해결이 됩니다. 

코드가 조금 복잡해지고 성능이 살짝 나빠질 수도 있지만, 그 대신 타입 안전성과 상호운용성은 좋아집니다.

 

배열로 구현한 가장 간단한 버전

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를 원하는 타입으로 형변환해야 합니다. 
  • 혹시나 타입이 다른 원소가 들어 있었다면 런타임에 형변환 오류가 발생합니다. 

 

제네릭으로 변경 시도 1

public class Chooser<T> {
	private final T[] choiceArray;
    
    //생성자에 어떤 컬렉션을 넘기느냐에 따라 여러 용도로 사용될 수 있다. 
    public Chooser(Collection<T> choices) {
    	choiceArray = choices.toArray();		//컴파일 오류 발생
    }										//Object[] cannot be converted to T[]
    
    //컬렉션 안의 원소 중 하나를 무작위로 선택해 반환
    public Object choose() {
    	Random rnd = ThreadLocalRandom.current();
        return choiceArray[rnd.nextInt(choiceArray.length)];
    }
}

컴파일 오류 발생 (Object[] cannot be converted to T[])

Object 배열을 T 배열로 형 변환

choiceArray = (T[]) choices.toArray();     //unchecked cast 경고 발생

 

 

unchecked cast 경고 발생

choiceArray = (T[]) choices.toArray();     //unchecked cast 경고 발생

 제네릭에서는 런타임 시 원소의 타입 정보가 소거되므로, T가 무슨 타입인지 알 수 없어 컴파일러는 이 형변환이 런타임에도 안전한지 보장할 수 없다는 메세지입니다.

 

이와 같이 단지 컴파일러가 안전을 보장하지 못할 뿐인 경고는 코드를 작성하는 사람이 안전하다고 확신하다면 주석을 남기고 애너테이션을 달아 경고를 숨겨도 됩니다. 

 

하지만, 애초에 경고의 원인을 제거하는 편이 훨씬 낫습니다. 

비검사 형변환 경고를 제거하려면, 배열 대신 리스트를 쓰면 됩니다.

private final List<T> choiceList;

 

 

제네릭으로 변경 완료 - 안전성 확보

public class Chooser<T> {
	private final List<T> choiceList;
    
    //생성자에 어떤 컬렉션을 넘기느냐에 따라 여러 용도로 사용될 수 있다. 
    public Chooser(Collection<T> choices) {
    	choiceArray = new ArrayList<>(choices);
    }										
    
    //컬렉션 안의 원소 중 하나를 무작위로 선택해 반환
    public Object choose() {
    	Random rnd = ThreadLocalRandom.current();
        return choiceList.get(rnd.nextInt(choiceList.size()));
    }
}

 

 

 


출처

https://www.yes24.com/Product/Goods/65551284

 

이펙티브 자바 Effective Java 3/E - 예스24

자바 플랫폼 모범 사례 완벽 가이드 - Java 7, 8, 9 대응자바 6 출시 직후 출간된 『이펙티브 자바 2판』 이후로 자바는 커다란 변화를 겪었다. 그래서 졸트상에 빛나는 이 책도 자바 언어와 라이브

www.yes24.com