Skip to content

Latest commit

 

History

History
115 lines (88 loc) · 4.96 KB

item26_27_28_31.md

File metadata and controls

115 lines (88 loc) · 4.96 KB

ITEM 26 : 로 타입은 사용하지 말라

List 같은 매개변수화 타입을 사용할 때와 달리 List 같은 로 타입을 사용하면 타입 안전성을 읽게 된다.

public static void main(String[] args) {

        List<String> strings = new ArrayList<>();
        unsafeAdd(strings, 42);
        String s = strings.get(0);

    }

    private static void unsafeAdd(List list, Object o) {
        list.add(o); // Run time 에 ClassCastException 발생한다
    }

와일트카드 타입은 안전하고, 로 타입은 안전하지 않다. 로 타입은 컬렉션에는 아무 원소나 넣을 수 있으니 타입 불변식을 훼손하기 쉽다. 반면 Collection<?>에는 (null 외에는) 어떤 원소도 넣을 수 없다.

핵심 정리

로 타입을 사용하면 런타임에 예외가 일어날 수 있으니 사용하면 안 된다. 로 타입은 제네릭이 도입되기 이전 코드와의 호환성을 위해 제공될 뿐이다.

ITEM 27 : 비검사 경고를 제거하라

핵심 정리

비검사 경고는 중요하니 무시하지 말자. 모든 비검사 경고는 런타임에 ClassCastException을 일으킬 수 있는 잠ㅁ재적 가능성을 뜻하니 최선을 다해 제거하라. 경고를 없앨 방법을 찾지 못하겠다면, 그 코드가 타입 안전함을 증명하고 가능한 한 범위를 좁혀 @SuppressWarnings("unchecked") 애너테이션으로 경고를 숨겨라. 그런 다음 경고를 숨기기로 한 근거를 주석으로 남겨라.

ITEM 28 : [제너릭]배열보다는 리스트를 사용하라

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

Object[] objectArray = new Long[1]; // 에러 경고만을 보여줌
objectArray[0] = ""; // ArrayStoreException을 던진다.

List<Object>[] ol = new ArrayList<Long>(); // 컴파일에서 에러를 보여줌
ol.add("");

배열은 실체화 된다. 배열은 런타임에도 자신이 담기로 한 원소의 타입을 인지하고 확인한다. 반면 제네릭은 타입 정보가 런타임에는 소거된다.

// before
public static void main(String... args) {
  Object[] array = new Long[10];
  array[0] = 1L;

  List<String> list = new ArrayList<>();
  list.add("Hi");
}

// after
public static void main(String... var0) {
  //컴파일 후 배열은 실체화가 된다.
  Long[] var2 = new Long[10];
  var2[0] = Long.valueOf(1L);

  //컴파일 후 리스트에서는 타입이 소거된다.
  ArrayList var1 = new ArrayList();
  var1.add("Hi");
}

핵심 정리

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

ITEM 30 : 이왕이면 제네릭 메서드로 만들라

public static <E> Set<E> union(Set<E> s1, Set<E> s2) {
    Set<E> result = new HashSet<>(s1);
    result.addAll(s2);
    return result;
    }

union 메서드는 집합 3개(입력 2개, 반환 1개)의 타입이 모두 같아야 한다. 이를 한정적 와일드카드 타입을 사용하여 더 유연하게 개선할 수 있다.

ITEM 31 : 한정적 와일드카드를 사용해 API 유연성을 높이라

매개변수화 타압(ex. List)은 불공변이다. 즉, 서로 다른 타입 Type1과 Type2가 있을 때 List은 List의 하위 타입도 상위 타입도 아니다.

생산자(producer) 매개변수에 와일드 카드 타입 적용

    //E와 같은 타입이거나 하위 타입이면 실행돤다.
    public void pushAll(Iterable<? extends E> src) {
        for (E e : src) {
            push(e);
        }
    }

소비자(consumer) 매개변수에 와일드카드 타입 적용

//E와 같은 타입이거나 상위 타입이면 실행된다.
public void popAll(Collection<? super E> dst) {
    while (!isEmpty()) {
        dst.add(pop())
    }
}
  • 매개변수화 타입 T가 생산자라면 <? extends T>를 사용하고, 소비자라면 <? super T>를 사용하라.
  • 입력 매개변수가 생산자와 소비자 역활을 동시에 한다면 와일드카드 타입을 써도 좋을 게 없다.
  • 클래스 사용자가 와일드카드 타입을 신경 써야 한다면 그 API에 무슨 문제가 있을 가능성이 크다.

핵심 정리

조금 복잡하더라고 와일드카드 타입을 적용하면 API가 훨씬 유연해진다. 그러니 널리 쓰일 라이브러리를 작성한다면 반드시 와일드카트 타입을 적절히 사용해줘야 한다. PECS공식을 기억하자. 즉 생산자(producer)는 extends를 소비자(consumer)는 super를 사용한다. Comparable과 Comparator는 모두 소비자라는 사실도 잊지 말자.