tonykang22 / study

0 stars 0 forks source link

[이펙티브 자바] 아이템 31. 한정적 와일드카드를 사용해 API 유연성을 높이라. #153

Open tonykang22 opened 1 year ago

tonykang22 commented 1 year ago

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

핵심 정리 1: Chooser와 Union API 개선

PECS: Producer-Extends, Consumer-Super



예시

public class Stack<E> {

    private E[] elements;
    private int size = 0;

...

// Collection<Number>만 받을 수 있다. 즉, Collection<Object>는 받을 수 없다.
//    public void popAll(Collection<E> dst) {
//        while (!isEmpty())
//            dst.add(pop());
//    }

    // 하위 한정 와일드카드 타입 적용 (= E 타입의 super type 허용)
    public void popAll(Collection<? super E> dst) {
        while (!isEmpty())
            dst.add(pop());
    }

    public static void main(String[] args) {
        Stack<Number> numberStack = new Stack<>();
        Iterable<Integer> integers = Arrays.asList(3, 1, 4, 1, 5, 9);
        numberStack.pushAll(integers);

        Iterable<Double> doubles = Arrays.asList(3.1, 1.0, 4.0, 1.0, 5.0, 9.0);
        numberStack.pushAll(doubles);

        Collection<Object> objects = new ArrayList<>();
        numberStack.popAll(objects);

        System.out.println(objects);
    }
}



핵심 정리 2: Comparator와 Comparable은 소비자



예시

public class Union {

    // 여기의 set은 Producer이므로 Set<? extends E> s1, Set<? extends E> s2로 작성하면 더 자유롭게 사용 가능
    public static <E> Set<E> union(Set<E> s1,
                                   Set<E> s2) {
        Set<E> result = new HashSet<>(s1);
        result.addAll(s2);
        return result;
    }

    // 향상된 유연성을 확인해주는 맛보기 프로그램 (185쪽)
    public static void main(String[] args) {
        Set<Integer> integers = new HashSet<>();
        integers.add(1); 
        integers.add(3); 
        integers.add(5); 

        Set<Double> doubles =  new HashSet<>();
        doubles.add(2.0); 
        doubles.add(4.0); 
        doubles.add(6.0); 

        Set<Number> numbers = union(integers, doubles);

// 위의 E extends ? 로 변경하면 해당 코드 실행 가능
//      Set<Number> numbers = Union.<Number>union(integers, doubles);

        System.out.println(numbers);
    }
}



더 상위 타입에서 구현 중인 경우

public class Box<T extends Comparable<T>> implements Comparable<Box<T>> {

    protected T value;

    public Box(T value) {
        this.value = value;
    }

    public void change(T value) {
        this.value = value;
    }

    @SuppressWarnings("unchecked")
    @Override
    public int compareTo(Box anotherBox) {
        return this.value.compareTo((T)anotherBox.value);
    }

    @Override
    public String toString() {
        return "Box{" +
                "value=" + value +
                '}';
    }
}
public class IntegerBox extends Box<Integer> {

    private final String message;

    public IntegerBox(int value, String message) {
        super(value);
        this.message = message;
    }

    @Override
    public String toString() {
        return "IntegerBox{" +
                "message='" + message + '\'' +
                ", value=" + value +
                '}';
    }
}
public class RecursiveTypeBound {
    public static <E extends Comparable<? super E>> E max(List<? extends E> list) {
        if (list.isEmpty())
            throw new IllegalArgumentException("빈 리스트");

        E result = null;
        for (E e : list)
            if (result == null || e.compareTo(result) > 0)
                result = e;

        return result;
    }

    public static void main(String[] args) {
        List<IntegerBox> list = new ArrayList<>();
        list.add(new IntegerBox(10, "keesun"));
        list.add(new IntegerBox(2, "whiteship"));

        System.out.println(max(list));
    }
}



핵심 정리 3: 와일드카드 활용 팁



예시

public class Swap {

    // E 타입의 의미: 타입을 알고 있다.
    public static <E> void swap(List<E> list, int i, int j) {
// 비한정적 와일드카드로 변경 시 전달이 불가능해진다. (비한정적 와일드카드 = 타입을 알지 못한다.)
//    public static void swap(List<?> list, int i, int j) {
        list.set(i, list.set(j, list.get(i)));
//        swapHelper(list, i, j);
    }

    // 와일드카드 타입을 실제 타입으로 바꿔주는 private 도우미 메서드 (굳이 "?"를 사용하기 위해 Helper 메서드를 사용해야 할까...)
//    private static <E> void swapHelper(List<E> list, int i, int j) {
//        list.set(i, list.set(j, list.get(i)));
//    }

    public static void main(String[] args) {
        List<String> argList = Arrays.asList(args);
        swap(argList, 0, argList.size() - 1);
        System.out.println(argList);
    }
}
tonykang22 commented 1 year ago

완벽 공략 44. 타입 추론



예시

public class Box<T> {

    private T t;

    public T get() {
        return t;
    }

    public <E extends T> void set(E e) {
        this.t = e;
    }
}
public class BoxExample {

    private static <U> void addBox(U u, List<Box<U>> boxes) {
        Box<U> box = new Box<>();
        box.set(u);
        boxes.add(box);
    }

    private static <U> void outputBoxes(List<Box<U>> boxes) {
        int counter = 0;
        for (Box<U> box: boxes) {
            U boxContents = box.get();
            System.out.println("Box #" + counter + " contains [" +
                    boxContents.toString() + "]");
            counter++;
        }
    }

    private static void processStringList(List<String> stringList) {

    }

    public static void main(String[] args) {
        // 명시하지 않아도 타입 추론을 하기 때문에 우항에 <> (다이아몬드 연산자)만 사용하면 된다.
        ArrayList<Box<Integer>> listOfIntegerBoxes = new ArrayList<>();

        // Explicit type arguments (type witness): 직접 명시해도 되지만, 타입 추론으로 인해 하지 않아도 된다.
        BoxExample.<Integer>addBox(10, listOfIntegerBoxes);
        BoxExample.addBox(20, listOfIntegerBoxes);
        BoxExample.addBox(30, listOfIntegerBoxes);
        BoxExample.outputBoxes(listOfIntegerBoxes);

        // Target Type (Collections.emptyList() 생성 시, target의 type을 보고 추론해 만들어준다.)
        List<String> stringlist = Collections.emptyList();
        List<Integer> integerlist = Collections.emptyList();

        BoxExample.processStringList(Collections.emptyList());
    }
}