ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [Effective Java] 아이템28: 배열보다는 리스트를 사용하라
    Language/Java 2022. 7. 30. 02:29

     

    ✔️ 배열 vs 제네릭 타입

    1. 배열은 공변(covariant)인 반면, 제네릭은 불공변(invariant)이다.

    Sub가 Super의 하위 타입이라면 배열 Sub[]는 배열 Super[]의 하위 타입이 되지만, List<Sub>은 List<Super>의 하위 타입도 아니고 상위 타입도 아니다.

     

    2. 배열은 실체화(reify)된다.

    배열은 런타임에도 원소의 타입을 확인한다. 즉, 런타임예외가 발생할 수 있다.
    반면, 제네릭은 타입 정보가 런타임에는 소거된다. 원소 타입을 컴파일타임에만 검사하며 런타임에는 알 수조차 없다.

     

    💡 위와 같은 차이로 인해, 배열과 제네릭은 잘 어우러지지 못한다.

    예를 들어, 배열은 제네릭 타입(new List<E>[]), 매개변수화 타입(new List<String>[]), 타입 매개변수(new E[])로 사용할 수 없다.

     

     

    ✔️ Chooser 클래스 예제

    생성자에서 컬렉션을 받고, 컬렉션 안의 원소 중 하나를 무작위로 선택해 반환하는 choose 메소드를 제공하는 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를 원하는 타입으로 형변환해야 한다.
    이때 만약 다른 타입의 원소가 들어 있었다면 런타임에 형변환 오류가 발생할 것이다.

    따라서 이 클래스는 제네릭 타입으로 만들어야 한다.

     

     

    📍 제네릭으로 만들기 위한 시도 → 경고

    public class Chooser<T> { 
    	
        private final T[] choiceArray;
        
        public Chooser(Collection<T> choices) {
        	
            // 1. 컴파일 오류
            // choiceArray = choices.toArray();	
            
            // 2. 경고
            choiceArray = (T[]) choices.toArray();
        }
        
        public Object choose() {
            Random rnd = ThreadLocalRandom.current();
            return choiceArray[rnd.nextInt(choiceArray.length)];
        }
    }

    1번과 같이 작성할 경우, 배열 Object[]이 배열 T[]과 타입이 맞지 않아 컴파일 오류가 발생한다.

    이를 해결하기 위해 2번과 같이 작성한 경우, 오류는 발생하지 않지만 런타임에 형변환이 안전한지 보장할 수 없다는 경고 메세지가 나온다. 
    이 코드의 경우, 동작하기는 하지만 컴파일러가 안전을 보장하지 못 한다.

     

     

    📍 리스트 기반 → 타입 안전성 확보

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

    다음 코드는 오류나 경고 없이 컴파일된다.

    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()));
        }
    }

     

     

     

     

    핵심 정리

     

    • 배열은 공변이고 실체화되는 반면, 제네릭은 불공변이고 타입 정보가 소거된다.
    • 배열은 런타임에는 타입 안전하지만 컴파일타임에는 그렇지 않다.
      제네릭은 그 반대다.
    • 이러한 이유로 배열과 제네릭을 섞어 쓰기란 쉽지 않다.
    • 둘을 섞어 쓰다가 컴파일 오류나 경고를 만나면, 가장 먼저 배열을 리스트로 대체하는 방법을 적용해보자.

     

     

     

     

    728x90

    댓글

Designed by Tistory.