0Pricing
Java Academy · 课时

API 中的通配符

设计灵活的泛型方法

API 中的通配符 是 CoddyKit 上的免费 Java Academy 课时。 这是第 4 节课,共 4 节。 你可以在下方免费阅读本课时的完整内容 — 然后在浏览器中使用内置代码编辑器和全天候 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 中的通配符」的完整文本可在网页上免费阅读。要进行交互式练习(内置代码编辑器和全天候 AI 导师)并解锁 Java Academy 课程的其余内容,请升级到 CoddyKit PRO。 Java Academy 课程共包含 4 节课。

「API 中的通配符」这节课中我会学到什么?

设计灵活的泛型方法 你通过在浏览器中直接运行的动手代码来练习 Java Academy,全天候 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