본문 바로가기

JAVA/Effective Java

item28) 배열보다는 리스트를 사용하라

728x90

불공변성(Invariant)

무공변성이란 상속 관계에 상관없이, 자기 타입만 허용하는 것

공변성(covariant)

공변성은 자기 자신과 자신을 상속한 하위 객체의 타입을 허용하는 것

반공변성(contravariant)

공변성의 반대 개념 자기 자신과 자신이 상속한 부모 객체만 허용하는 것

 

제네릭은 불공변, 배열은 공변이다.

public static void main(String[] args) {
        Object [] objects = new Long[10];
        objects[0] = "boo"; // ArrayStoreException 발생

        List<Object> objectList = new ArrayList<Long>(); // List 는 제네릭 타입으로 무공변이다. 컴파일 에러 발생
        objectList.add("boo");
    }

 

실체화 : 런타임에도 자신이 담기로 한 원소의 타입을 체크함.

배열 : 실체화, 제네릭 : 실체화 불가 타입

 

이러한 차이 떄문에 배열은 제네릭과 어울리지 않음(배열은 제네릭 불가 타입)

배열 : 공변, 실체화 (컴파일 타임 타입 안전 X, 런타임에 타입 안전)

제네릭 : 불공변, 타입 소거(컴파일 타임 타입 안전, 런타임 타입 안전 X)

 

제네릭 배열을 허용할 경우, 형변환 코드에서 런타임 ClassCastException 발생 가능 이는 컴파일 타임에 타입 체크를 해서 런타임에 ClassCastException이 발생하는 걸 막겠다는 제네릭의 취지와 맞지 않음

 

List <String>[] stringLists = new List<String>[1]; 
List <Integer> intList = List.of(42); //원소가 하나인 List<Integer>
Object[] objects = stringLists; // 배열은 공변이므로 Object 배열에 List<String>배열 할당가능
objects[0] = intList; // 타입이 런타임에 소거되므로 마찬가지로 가능
String s = stringLists[0].get(0); // ClassCastException 발생. int -> String 으로 바꾸려고하기 떄문


 

 배열로 형변혼할 때 제너릭 배열 생성오류나 비검사 형변환 경고가 뜨는 경우 대부분 배열 대신 컬렉션인 List<E>를 사용하면 해결된다, 코드가 조금 복잡해지고 성능이 살짝 나빠지지만 타입안전성과 상호 운용성이 좋아진다.

/* 
제네릭 없는 v0 초기버전
choose 호출할때마다 해당 타입으로 형변환 필요
*/

public class Chooser{
  private final Object[] choiceArray;

  public Chooser(Collection choices){
    choicesArra = choices.toArray; 
  }

    public Object choose(){
    Random rnd = ThreadLocalRandom.current();
    return choiceArray[rnd.nextInt(choiceArray.length)];
  }
}

/*v1 제너릭 적용 

생성자에서 unchecked cast 비검사 경고 발생
*/

public class Chooser<T>{
  private final T[] choiceArray;

  public Chooser(Collection<T> choices){
    choicesArra = (T[]) choices.toArray; 
  }

    public Object choose(){
    Random rnd = ThreadLocalRandom.current();
    return choiceArray[rnd.nextInt(choiceArray.length)];
  }
}


/*v2 배열 대신 리스트 적용*/
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()));
  }
}