0Pricing
Java Academy · 강의

API에서 와일드카드 사용

유연한 제네릭 메서드를 설계합니다

API에서 와일드카드 사용은(는) CoddyKit의 무료 Java Academy 강의입니다. 이것은 4개 중 4번째 강의입니다. 아래에서 전체 강의를 무료로 읽을 수 있으며, 내장 코드 에디터와 24/7 AI 튜터와 함께 브라우저에서 직접 실습할 수 있습니다. 이 강의는 Java Academy 학습 경로의 일부이며, 진행 상황이 웹과 CoddyKit 앱에 동기화됩니다. Java Academy 강의에는 총 4개의 강의가 포함되어 있습니다.

유연한 메서드 설계

와일드카드는 주로 API 설계자를 위한 도구입니다. 와일드카드를 적절히 배치하면 호출자가 형 변환 없이 더 다양한 형식을 전달할 수 있습니다.

목표는 호출자에게는 최대한의 유연성을 제공하고 내부에서는 완전한 형식 안전성을 유지하는 것입니다.

import java.util.List;

public class Main {
    static double total(List<? extends Number> items) {
        double sum = 0;
        for (Number n : items) sum += n.doubleValue();
        return sum;
    }
    public static void main(String[] args) {
        System.out.println(total(List.of(1, 2, 3)));
        System.out.println(total(List.of(1.5, 2.5)));
    }
}

제한 없는 와일드카드

List<?>는 제한 없는 와일드카드로, 정체를 알 수 없는 어떤 형식의 리스트를 의미합니다.

메서드가 요소 형식을 신경 쓰지 않을 때 사용합니다. 예를 들어 요소 개수를 세거나 비어 있는지 확인할 때 적합합니다.

import java.util.List;

public class Main {
    static int size(List<?> any) {
        return any.size();
    }
    public static void main(String[] args) {
        System.out.println(size(List.of("a", "b")));
        System.out.println(size(List.of(1, 2, 3)));
    }
}

List<?>와 List<Object>의 차이

두 형식은 서로 다릅니다. List<Object>는 Object로 선언된 리스트만 받습니다. List<?>는 어떤 형식의 리스트든 받습니다.

List<String>은 List<?> 매개변수에 전달할 수 있지만 List<Object> 매개변수에는 전달할 수 없습니다.

import java.util.List;

public class Main {
    static void printAll(List<?> list) {
        for (Object o : list) System.out.println(o);
    }
    public static void main(String[] args) {
        List<String> names = List.of("Ada", "Linus");
        printAll(names); // works; List<Object> would reject this
    }
}

형식 매개변수가 더 명확한 경우

요소 형식을 두 번 이상 참조해야 한다면 와일드카드보다 이름이 있는 형식을 사용하는 편이 읽기 좋습니다.

void swap(List<?> l, int i, int j)는 비공개 도우미가 필요하지만, 깔끔한 제네릭 버전과 비교해 볼 수 있습니다.

import java.util.ArrayList;
import java.util.List;

public class Main {
    static <T> void swap(List<T> list, int i, int j) {
        T tmp = list.get(i);
        list.set(i, list.get(j));
        list.set(j, tmp);
    }
    public static void main(String[] args) {
        List<String> list = new ArrayList<>(List.of("a", "b", "c"));
        swap(list, 0, 2);
        System.out.println(list);
    }
}

도우미를 이용한 캡처 기법

공개 API에서 List<?>를 사용해야 한다면 비공개 제네릭 도우미에 위임하여 와일드카드를 캡처할 수 있습니다.

도우미가 형식 T에 이름을 붙이므로 리스트에 다시 쓸 수 있습니다.

import java.util.ArrayList;
import java.util.List;

public class Main {
    public static void reverse(List<?> list) { reverseHelper(list); }
    private static <T> void reverseHelper(List<T> list) {
        for (int i = 0, j = list.size() - 1; i < j; i++, j--) {
            T tmp = list.get(i);
            list.set(i, list.get(j));
            list.set(j, tmp);
        }
    }
    public static void main(String[] args) {
        List<Integer> list = new ArrayList<>(List.of(1, 2, 3, 4));
        reverse(list);
        System.out.println(list);
    }
}

실제 API의 생산자 매개변수

호환되는 모든 원본에서 가져오는 addAll을 설계하려면 원본에 상한을 사용합니다.

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

public class Main {
    static <T> void addAll(List<T> target, Collection<? extends T> source) {
        for (T t : source) target.add(t);
    }
    public static void main(String[] args) {
        List<Number> nums = new ArrayList<>();
        addAll(nums, List.of(1, 2, 3));
        System.out.println(nums);
    }
}

실제 API의 소비자 매개변수

결과를 쓰는 수신처 API는 대상에 하한을 사용하므로 호출자가 더 넓은 컨테이너를 제공할 수 있습니다.

import java.util.ArrayList;
import java.util.List;

public class Main {
    static <T> void repeat(T value, int times, List<? super T> sink) {
        for (int i = 0; i < times; i++) sink.add(value);
    }
    public static void main(String[] args) {
        List<Object> out = new ArrayList<>();
        repeat("hi", 3, out);
        System.out.println(out);
    }
}

중첩 제네릭과 와일드카드

중첩 제네릭에는 와일드카드가 필요한 경우가 많습니다. 모든 종류의 리스트를 담은 리스트를 받는 메서드는 List<? extends List<?>>를 사용합니다.

import java.util.List;

public class Main {
    static int countAll(List<? extends List<?>> lists) {
        int total = 0;
        for (List<?> inner : lists) total += inner.size();
        return total;
    }
    public static void main(String[] args) {
        System.out.println(countAll(List.of(List.of(1, 2), List.of("a", "b", "c"))));
    }
}

과도한 제네릭화 피하기

모든 메서드에 와일드카드가 필요한 것은 아닙니다. 호출자가 항상 하나의 형식만 전달한다면 일반 매개변수가 더 명확합니다.

불변성 때문에 실제 호출자가 막히는 상황을 확인했을 때 와일드카드를 추가하고, 미리 넣지는 마십시오.

import java.util.List;

public class Main {
    // Simple and clear; no wildcard needed for this internal use
    static String joinStrings(List<String> parts) {
        return String.join(", ", parts);
    }
    public static void main(String[] args) {
        System.out.println(joinStrings(List.of("a", "b", "c")));
    }
}

와일드카드 의도 문서화

와일드카드는 의도를 나타냅니다. ? extends T는 “이 값에서는 읽기만 한다”는 뜻입니다. ? super T는 “이 값에는 쓰기만 한다”는 뜻입니다.

올바른 와일드카드를 선택하면 계약을 강제하면서 코드 자체로 의도를 설명할 수 있습니다.

import java.util.ArrayList;
import java.util.List;

public class Main {
    // src: read-only producer; dest: write-only consumer
    static <T> void transfer(List<? extends T> src, List<? super T> dest) {
        for (T t : src) dest.add(t);
    }
    public static void main(String[] args) {
        List<Number> dest = new ArrayList<>();
        transfer(List.of(1, 2), dest);
        System.out.println(dest);
    }
}

제네릭 Stack API

Bloch가 제시한 유명한 스택 예제입니다. pushAll은 생산자에서 값을 소비하고(extends), popAll은 소비자에 값을 생산합니다(super).

import java.util.ArrayDeque;
import java.util.Collection;
import java.util.Deque;
import java.util.ArrayList;
import java.util.List;

public class Main {
    static class Stack<E> {
        private final Deque<E> items = new ArrayDeque<>();
        void pushAll(Collection<? extends E> src) { for (E e : src) items.push(e); }
        void popAll(Collection<? super E> dst) { while (!items.isEmpty()) dst.add(items.pop()); }
    }
    public static void main(String[] args) {
        Stack<Integer> s = new Stack<>();
        s.pushAll(List.of(1, 2, 3));
        List<Number> out = new ArrayList<>();
        s.popAll(out);
        System.out.println(out);
    }
}

빠른 확인

API 설계 감각을 확인해 보십시오.

복습

와일드카드를 사용한 API 설계를 배웠습니다.

  • 형식에 구애받지 않는 읽기 전용 메서드에는 List<?>를 사용합니다.
  • 유연성을 위해 매개변수에 PECS를 적용합니다.
  • 와일드카드를 통해 써야 할 때는 비공개 도우미 캡처 기법을 사용합니다.
  • 반환 형식에는 와일드카드를 사용하지 말고 과도하게 제네릭화하지 마십시오.

와일드카드와 PECS 과정을 완료했습니다.

public class Main {
    public static void main(String[] args) {
        System.out.println("Wildcards in APIs course complete");
    }
}

자주 묻는 질문

“API에서 와일드카드 사용” 강의는 무료인가요?

네 — “API에서 와일드카드 사용” 전체 내용을 이 웹사이트에서 무료로 읽을 수 있습니다. 인터랙티브하게 실습하려면(내장 코드 에디터와 24/7 AI 튜터), CoddyKit PRO로 업그레이드하면 Java Academy 강의 전체를 잠금 해제할 수 있습니다. Java Academy 강의에는 총 4개의 강의가 포함되어 있습니다.

“API에서 와일드카드 사용”에서 뭘 배우나요?

유연한 제네릭 메서드를 설계합니다 브라우저에서 직접 실행하는 실습 코드로 Java Academy을(를) 배우며, 24/7 AI 튜터가 강의를 진행하면서 질문에 답변해줍니다.

Java Academy을(를) 시작하는 데 경험이 필요한가요?

사전 경험은 필요하지 않습니다. CoddyKit의 Java Academy은(는) 초급자부터 고급 학습자까지를 위해 구성되어 있으므로, 여기서 시작하거나 처음부터 시작할 수 있으며 자신의 속도대로 진행할 수 있습니다. 이것은 4개 중 4번째 강의입니다.

“API에서 와일드카드 사용” 강의는 얼마나 걸리나요?

대부분의 CoddyKit 강의는 약 5~10분이 소요됩니다. 각 강의는 간결하고 인터랙티브하여 꾸준한 진행이 가능하며, 웹과 앱에서 중단한 부분부터 바로 시작할 수 있습니다.

이 Java Academy 강의에서 코드를 작성하고 실행할 수 있나요?

네. 모든 Java Academy 강의에는 내장 코드 에디터가 포함되어 있으므로, 브라우저에서 바로 실제 코드를 작성하고 실행한 후 즉시 AI 피드백을 받을 수 있습니다 — 로컬 설정이 필요 없습니다.

이 강의의 모든 강의

  1. 상한 와일드카드
  2. 하한 와일드카드
  3. PECS 원칙
  4. API에서 와일드카드 사용
← Java Academy(으)로 돌아가기