Effective Java 2/E - Chatper 05 제네릭 (2)

Rule 28 - 한정적 와일드카드를 써서 API 유연성을 높여라

전제

List<Number>List<Integer> 간에는 부모-자식 관계가 성리될 수 없다. 즉 캐스팅이 불가능하다. 하지만 논리적(상식적)으로 두 Collection 간 putAll과 같은 메소드는 호출 가능해야 할 것 같다.

위와 같은 문제를 해결하고자 한정적 와일드카드 자료형(bounded wildcard type) 이 존재한다.

알아보자

example 한정적 와일드카드 자료형

// E 객체 생산자 역할을 하는 인자에 대한 와일드 카드 자료형
public void putAll(Iterable<? extends E> src) {
    for (E e : src)
        push(e);
}
// E의 소비자 구실을 하는 인자에 대한 와일드카드 자료형
public void popAll(Collection<? super E> dst) {
    while(!isEmpty())
        dst.add(pop());
}

PECS (Produce - Extends, Consumer - Super)

Stack 예제에서 pushAll의 인자 src는 스택이 사용할 E형의 객체를 만드는 생산자 이므로 src의 자료형은 Iterable<? extends E>가 되어야 한다. popAll의 dst는 Stack에 보관된 객체를 소비하므로 dst의 자료형은 Collection<? super E>가 되어야한다.

example

public static <T extends Comparable<? super T>> T max(List <? extends T> list)

Compareable의 경우 부모의 compare를 사용할 수 있으므로

나프탈린과 와들러는 이를 Get(Consumer) and Put(Produce) Principle이라고 표현한다.

반환값에는 와일드카드 자료형을 쓰면 안된다.

좀 더 유연한 코드를 만들 수 있도록 도와주기는 커녕, 클라이언트 코드 안에도 와일드카드 자료형을 명시해야 하기 때문이다.

명시적 형인자(explicit type parameter)

컴파일러가 자료형을 명확하게 유추하지 못할 경우 사용

before

Set<Integer> integers = ...;
Set<Double> doubles = ...;
Set<Number> numbers = Union.union(integers, doubles);

after

// 명시적 형인자 .<Number> 적용
Set<Number> numbers = Union.<Number>union(integers, doubles);

비한정 와일드카드 자료형 포착 helper

before

public static void swap(List<?> list, int i, int j) {
    list.set(i, list.set(j, list.get(i)));
    // 하지만 컴파일 오류 발생. <?>이기 때문에 null이외에는 값을 넣을 수가 없음
    // 해결하려면 명시적으로 타입을 지정해주거나 포착(capture)할 수 있게 해줘야함
}

after

public static void swap(List<? list, int i, int j) {
    swapHelper(list, j, i);
}

// 와일드카드 자료형을 포착하기 위한 private 도움 메서드
private static <E> void swapHelper(List<E> list, int i, int j) {
    list.set(i, list.set(j, list.get(i)));
}

결론

  • API에는 와일드카드 자료형을 사용해서 유연성을 높여라 - 필수다
  • PECS
  • Comparable, Comparator는 모두 소비자이다 - <? super T>

Rule 29 - 형 안전 다형성 컨테이너를 쓰면 어떨지 따져보라

example

// 형 안전 다형성(heterogeneous)컨테이너 패턴
public class Favorites {
    Map<Class<?>, Object> favroites = new HashMap<Class<?>, Object>();

    public <T> void putFavorite(Class<T> type, T instance) {
        if (type == null)
            throw new NullPointerException("Type is null");
        // before 타입안정성 사라짐
        // favorites.put(type, instance);
        // after 보완코드
        favorites.put(type, type.cast(instance));
    }

    public <T> T getFavorite(Class<T> type) {
        // 타입안정성 복구 - 동적 형변환
        return type.cast(favorites.get(type));
    }
}

형 안전 다형성 컨테이너 패턴의 문제점

  1. Class(raw type)를 인자로 넘기면 형이 지정되지 않았기 때문에 형 안정성이 깨질 수 있음
    1. favorites.put(type, type.cast(instance)); 로 커버
  2. 실체화 불가능 자료형에는 쓰일 수 없음. List<String>.class는 존재할 수 없음
    1. 실질적으로 완벽한 솔루션은 없음
    2. 외부 라이브러리에 ParameterizedType과 같은 헬퍼가 있긴 하나 라이브러리 별로 종속적임

asSubClass

실행시점에 특정한 Class객체를 인자로 주어진 하위클래스의 Class객체로 형변환 해준다.

  • 성공하면 Class 객체 반환
  • 형변환이 불가능하면 ClassCastException 발생

결론

컨테이너 대신 키를 제네릭으로 만들면 형인자 개수가 고정되는 제약없는 형 안전 다형성 컨테이를 만들 수 있다. 그런경우 Class 객체(자료형 토큰)를 키로 쓰면되며, Column<T>와 같이 직접 구현할 수도 있다.

MJ

MJ
Backend 개발자 사람입니다. 어플리케이션의 복잡성을 다루는 DDD에 관심이 많습니다. 어제보다 더 나은 개발자가 되려고 항상 노력합니다.

spring boot 2.4.x 에서 openfeign + hystrix 통합하기

spring-boot 2.4.x spring-cloud 2020.x 의존성 상황에서 feign.hystrix.enabled=true가 안됨`feign.circuitbreaker.enabled=true` 로 바꿔보지만 openfeign과 hystr...… Continue reading

IDDD 14장. 애플리케이션

Published on June 19, 2018