Item 31. Wildcard Type

ν•œμ •μ  μ™€μΌλ“œμΉ΄λ“œλ₯Ό μ‚¬μš©ν•΄ API μœ μ—°μ„±μ„ 높이라

item 28μ—μ„œ μ–ΈκΈ‰ν–ˆλ“― λ§€κ°œλ³€μˆ˜ν™” νƒ€μž…μ€ λΆˆκ³΅λ³€(invariant)인 뢀뢄에 λŒ€ν•΄ μ˜λ¬Έμ μ„ κ°€μ§ˆ 수 μžˆλ‹€. List<String>은 List<Object>의 ν•˜μœ„ νƒ€μž…μ΄ μ•„λ‹Œλ°, List<String>은 List<Object>κ°€ ν•˜λŠ” 일을 μ œλŒ€λ‘œ μˆ˜ν–‰ν•  수 μ—†κΈ° λ•Œλ¬Έμ— λ¦¬μŠ€μ½”ν”„ μΉ˜ν™˜ 원칙에 λ”°λ₯΄λ©΄ λΆˆκ³΅λ³€μΈ 것이 νƒ€λ‹Ήν•˜λ‹€.

ν•˜μ§€λ§Œ μ΄λŒ€λ‘œ μ‚¬μš©ν•˜κΈ°μ—” λΆˆνŽΈν•œ 점이 λ§Žμ•„ λ§€κ°œλ³€μˆ˜ν™” νƒ€μž…μ„ μœ μ—°ν•˜κ²Œ μ‚¬μš©ν•  수 μžˆλ„λ‘ λ„μ™€μ£ΌλŠ” 방법이 μžˆλŠ”λ°, λ°”λ‘œ ν•œμ •μ  μ™€μΌλ“œμΉ΄λ“œ νƒ€μž…(bounded wildcard type)을 μ‚¬μš©ν•˜λŠ” 것이닀.

import java.util.Collection;

class Stack<E> {

    // ...

    public void push(E e) {
        elements[size++] = e;
    }

    public void pushAll(Iterable<E> src) {
        for (E e : src) {
            push(e);
        }
    }

    public void pop(E e) {
        E result = elements[--size];
        elements[size] = null;
        return result;
    }

    public void popAll(Collection<E> dst) {
        while (!isEmpty()) {
            dst.add(pop());
        }
    }
}

class Main {

    public static void main(String[] args) {
        Stack<Number> numberStack = new Stack<>();
        Iterable<Integer> integers = ...;
        numberStack.pushAll(integers); // 컴파일 μ—λŸ¬, Iterable<Integer>λŠ” Iterable<Number>의 ν•˜μœ„ νƒ€μž…μ΄ μ•„λ‹˜

        Collection<Object> objects = ...;
        numberStack.popAll(objects); // 컴파일 μ—λŸ¬, Collection<Object>λŠ” Collection<Number>의 ν•˜μœ„ νƒ€μž…μ΄ μ•„λ‹˜
    }
}

IntegerλŠ” Number의 ν•˜μœ„ νƒ€μž…μ΄λ‹ˆ λ…Όλ¦¬μ μœΌλ‘œ 잘 λ™μž‘ν•  것 κ°™μ§€λ§Œ, μ œλ„€λ¦­μ˜ λΆˆκ³΅λ³€ νŠΉμ„±μœΌλ‘œ 인해 컴파일 μ—λŸ¬κ°€ λ°œμƒν•œλ‹€. ν•΄κ²°μ±…μœΌλ‘œλŠ” μ•žμ˜ μ•„μ΄ν…œλ“€μ—μ„œ μ–ΈκΈ‰ν–ˆλ“―μ΄ ν•œμ •μ  μ™€μΌλ“œμΉ΄λ“œ νƒ€μž…μ„ μ‚¬μš©ν•˜λŠ” 것이닀.

class Stack<E> {

    // ...

    public void push(E e) {
        elements[size++] = e;
    }

    public void pushAll(Iterable<? extends E> src) { // ν•œμ •μ  μ™€μΌλ“œμΉ΄λ“œ νƒ€μž… 적용
        for (E e : src) {
            push(e);
        }
    }

    public void pop(E e) {
        E result = elements[--size];
        elements[size] = null;
        return result;
    }

    public void popAll(Collection<? super E> dst) { // ν•œμ •μ  μ™€μΌλ“œμΉ΄λ“œ νƒ€μž… 적용
        while (!isEmpty()) {
            dst.add(pop());
        }
    }
}
  • Iterable<? extends E>

E의 ν•˜μœ„ νƒ€μž…μ„ λͺ¨λ‘ ν¬ν•¨ν•˜λŠ” Iterable νƒ€μž…μ„ λ§€κ°œλ³€μˆ˜λ‘œ 받을 수 있게 λ˜μ–΄, Number의 ν•˜μœ„ νƒ€μž…μΈ Integerλ₯Ό ν¬ν•¨ν•˜λŠ” Iterable νƒ€μž…μ„ λ§€κ°œλ³€μˆ˜λ‘œ 받을 수 있게 λ˜μ—ˆλ‹€.

  • Collection<? super E>

E의 μƒμœ„ νƒ€μž…μ„ λͺ¨λ‘ ν¬ν•¨ν•˜λŠ” Collection νƒ€μž…μ„ λ§€κ°œλ³€μˆ˜λ‘œ 받을 수 있게 λ˜μ–΄, Number의 μƒμœ„ νƒ€μž…μΈ Objectλ₯Ό ν¬ν•¨ν•˜λŠ” Collection νƒ€μž…μ„ λ§€κ°œλ³€μˆ˜λ‘œ 받을 수 있게 λ˜μ—ˆλ‹€.

이처럼 ν•œμ •μ  μ™€μΌλ“œμΉ΄λ“œ νƒ€μž…μ„ μ‚¬μš©ν•˜λ©΄ λ§€κ°œλ³€μˆ˜ν™” νƒ€μž…μ΄ λΆˆκ³΅λ³€μ΄λΌλ„ μœ μ—°ν•˜κ²Œ μ‚¬μš©ν•  수 있게 λœλ‹€.

PECS(Producer-Extends, Consumer-Super)

ν•œμ •μ  μ™€μΌλ“œμΉ΄λ“œ νƒ€μž…μ„ μ‚¬μš©ν•˜λ©΄ λ§€κ°œλ³€μˆ˜ν™” νƒ€μž…μ΄ λΆˆκ³΅λ³€μ΄λΌλ„ μœ μ—°ν•˜κ²Œ μ‚¬μš©ν•  수 있게 λ˜μ—ˆμ§€λ§Œ, 이λ₯Ό μ‚¬μš©ν•  λ•Œ μ£Όμ˜ν•  점이 μžˆλ‹€. λ°”λ‘œ μƒμ‚°μž(producer)와 μ†ŒλΉ„μž(consumer) 역할에 따라 extends와 superλ₯Ό 적절히 μ‚¬μš©ν•΄μ•Ό ν•œλ‹€λŠ” 것이며, κ·Έ 원칙은 λ‹€μŒκ³Ό κ°™λ‹€.

  • λ§€κ°œλ³€μˆ˜ν™” νƒ€μž… Tκ°€ μƒμ‚°μžμΈ 경우: <? extends T>

  • λ§€κ°œλ³€μˆ˜ν™” νƒ€μž… Tκ°€ μ†ŒλΉ„μžμΈ 경우: <? super T>

  • λ§€κ°œλ³€μˆ˜ν™” νƒ€μž… Tκ°€ μƒμ‚°μžμ™€ μ†ŒλΉ„μžμΈ 경우: T

  • λ°˜ν™˜ νƒ€μž…: ν•œμ •μ  μ™€μΌλ“œμΉ΄λ“œ νƒ€μž… 적용 X, ν΄λΌμ΄μ–ΈνŠΈ μ½”λ“œμ— μ™€μΌλ“œμΉ΄λ“œ νƒ€μž…μ΄ μ „νŒŒλ˜κΈ° λ•Œλ¬Έμ— ν΄λΌμ΄μ–ΈνŠΈ μ½”λ“œκ°€ 더 λ³΅μž‘ν•΄μ§

μ•žμ„œ μ–ΈκΈ‰ν•œ Stack μ˜ˆμ œμ—μ„œλ„ μƒμ‚°μž(pushAll)와 μ†ŒλΉ„μž(popAll) 역할에 따라 extends와 superλ₯Ό μ‚¬μš©ν–ˆλ‹€. μœ„ 두 상황이 μ•„λ‹Œ, μž…λ ₯ λ§€κ°œλ³€μˆ˜κ°€ μƒμ‚°μžμ™€ μ†ŒλΉ„μž 역할을 λ™μ‹œμ— ν•΄μ•Ό ν•˜λŠ” 상황에선 μ™€μΌλ“œμΉ΄λ“œ νƒ€μž…μ„ 쓰지 μ•ŠλŠ” 것이 μ’‹λ‹€.

μœ„ 곡식은 item 30μ—μ„œ μ‚΄νŽ΄λ³΄μ•˜λ˜ Collections.max λ©”μ„œλ“œμ—λ„ 이미 μ μš©λ˜μ–΄ μžˆμŒμ„ μ•Œ 수 μžˆλ‹€.

// Comparable μΈν„°νŽ˜μ΄μŠ€
public interface Comparable<T> {
    int compareTo(T o);
}

// java.util.Collections의 max λ©”μ„œλ“œ
public class Collections {
    // Suppresses default constructor, ensuring non-instantiability.
    private Collections() {
    }

    // ...

    public static <T extends Object & Comparable<? super T>> T max(Collection<? extends T> coll) {
        Iterator<? extends T> i = coll.iterator();
        T candidate = i.next();

        while (i.hasNext()) {
            T next = i.next();
            if (next.compareTo(candidate) > 0)
                candidate = next;
        }
        return candidate;
    }

    // ...
}
  • Collection<? extends T> coll: μž…λ ₯ λ§€κ°œλ³€μˆ˜μ—μ„œ μƒμ‚°μž 역할을 ν•˜λ―€λ‘œ extendsλ₯Ό μ‚¬μš©

  • Comparable<? super T>: μž…λ ₯ λ§€κ°œλ³€μˆ˜λ₯Ό μ†ŒλΉ„ν•˜λ©΄μ„œ compareTo λ©”μ„œλ“œλ₯Ό ν˜ΈμΆœν•˜λ―€λ‘œ superλ₯Ό μ‚¬μš©

νƒ€μž… λ§€κ°œλ³€μˆ˜ vs μ™€μΌλ“œμΉ΄λ“œ

νƒ€μž… λ§€κ°œλ³€μˆ˜μ™€ μ™€μΌλ“œμΉ΄λ“œλŠ” νƒ€μž… λ‹€ν˜•μ„±μ— μžˆμ–΄ κ³΅ν†΅λ˜λŠ” 뢀뢄이 μžˆμ–΄ λ‘˜ 쀑 ν•˜λ‚˜λ₯Ό 선택해 μ‚¬μš©ν•  수 μžˆλŠ” κ²½μš°κ°€ μžˆλ‹€.

interface Swap {
    // 1. νƒ€μž… λ§€κ°œλ³€μˆ˜λ₯Ό μ‚¬μš©ν•œ λ©”μ„œλ“œ μ„ μ–Έ
    public static <E> void swap(List<E> list, int i, int j);

    // 2. μ™€μΌλ“œμΉ΄λ“œλ₯Ό μ‚¬μš©ν•œ λ©”μ„œλ“œ μ„ μ–Έ
    public static void swap(List<?> list, int i, int j);
}

μœ„ 처럼 λ©”μ„œλ“œ 선언에 νƒ€μž… λ§€κ°œλ³€μˆ˜κ°€ ν•œ 번만 λ‚˜μ˜€λŠ” κ²½μš°μ—” μ™€μΌλ“œμΉ΄λ“œλ‘œ λŒ€μ²΄ν•˜λŠ” 것이 쒋은 방법이 될 수 μžˆλ‹€. public API라면 두 번째 λ©”μ„œλ“œκ°€ μ–΄λ–€ νƒ€μž…μ˜ List도 받을 수 있기 λ•Œλ¬Έμ— 더 μœ μ—°ν•˜κ²Œ μ‚¬μš©ν•  수 있기 λ•Œλ¬Έμ΄λ‹€.

이λ₯Ό κ΅¬ν˜„ν•˜κΈ° μœ„ν•΄ 좔가적인 helper λ©”μ„œλ“œλ₯Ό κ΅¬ν˜„ν•΄μ•Ό ν•œλ‹€λŠ” 단점이 μžˆμ§€λ§Œ, ν΄λΌμ΄μ–ΈνŠΈλŠ” 이λ₯Ό μ•Œ ν•„μš”κ°€ μ—†μœΌλ―€λ‘œ λ¬Έμ œκ°€ λ˜μ§€ μ•ŠλŠ”λ‹€.

// μ§κ΄€μ μœΌλ‘œ κ΅¬ν˜„ν•œ swap λ©”μ„œλ“œ
class Swap1 {

    public static void swap(List<?> list, int i, int j) {
        list.set(i, list.set(j, list.get(i))); // 컴파일 μ—λŸ¬, List<?>μ—λŠ” null μ™Έμ—λŠ” μ–΄λ–€ 값도 넣을 수 μ—†μŒ
    }
}

// helper method μΆ”κ°€
class Swap2 {

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

    private static <E> void swapHelper(List<E> list, int i, int j) {
        list.set(i, list.set(j, list.get(i)));
    }
}

Last updated

Was this helpful?