0Pricing
Java Academy · 课时

下界通配符

面向消费者的 ? super T

下界通配符 是 CoddyKit 上的免费 Java Academy 课时。 这是第 2 节课,共 4 节。 你可以在下方免费阅读本课时的完整内容 — 然后在浏览器中使用内置代码编辑器和全天候 AI 导师进行实践。 这是 Java Academy 学习路径的一部分,你的进度在网页和 CoddyKit 应用中同步。 Java Academy 课程共包含 4 节课。

下界通配符

? super T 是下界通配符。它表示“某个未知类型,该类型是 T 或 T 的超类型”。

List<? super Integer> 可以指向 List<Integer>、List<Number> 或 List<Object>。

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

public class Main {
    public static void main(String[] args) {
        List<? super Integer> a = new ArrayList<Number>();
        List<? super Integer> b = new ArrayList<Object>();
        a.add(1);
        b.add(2);
        System.out.println(a + " " + b);
    }
}

写入消费者

使用 ? super Integer 时,您可以安全地添加任何 Integer(或 Integer 的子类型)。

无论列表的实际类型是什么,它至少都是某种 Integer 超类型的列表,因此 Integer 始终可以放入其中。

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

public class Main {
    static void addNumbers(List<? super Integer> dest) {
        for (int i = 1; i <= 3; i++) dest.add(i);
    }
    public static void main(String[] args) {
        List<Number> nums = new ArrayList<>();
        addNumbers(nums);
        System.out.println(nums);
    }
}

读取时得到对象

这种设计的取舍是:从 ? super T 列表中读取时,编译器只知道元素是 T 的某个未知超类型。

读取时唯一安全的类型是 Object。

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

public class Main {
    public static void main(String[] args) {
        List<? super Integer> list = new ArrayList<Number>();
        list.add(10);
        Object o = list.get(0); // only Object is guaranteed
        // Integer i = list.get(0); // would NOT compile
        System.out.println(o);
    }
}

为什么读取受到限制

List<? super Integer> 实际上可能是一个存放着字符串的 List<Object>。

因此,读取操作可能返回任意对象。编译器无法保证返回的是 Integer,所以读取结果只能是 Object。

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

public class Main {
    public static void main(String[] args) {
        List<Object> raw = new ArrayList<>();
        raw.add("a string");
        List<? super Integer> view = raw; // legal: Object is a supertype of Integer
        view.add(5);
        System.out.println(view.get(0).getClass().getSimpleName());
    }
}

消费者用例

当一个方法只消费(写入)值时,请使用 ? super T。

填充目标集合的方法就是经典用例。

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

public class Main {
    static void fill(List<? super String> dest, int count) {
        for (int i = 0; i < count; i++) dest.add("item" + i);
    }
    public static void main(String[] args) {
        List<Object> objs = new ArrayList<>();
        fill(objs, 3);
        System.out.println(objs);
    }
}

添加子类型同样可行

使用 ? super Number 时,您可以添加任何 Number 或其子类型:整数、双精度浮点数、长整数。

下界保证列表至少能够接受 Number,因此所有子类型都可以放入其中。

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

public class Main {
    public static void main(String[] args) {
        List<? super Number> list = new ArrayList<Object>();
        list.add(1);      // Integer
        list.add(2.5);    // Double
        list.add(3L);     // Long
        System.out.println(list);
    }
}

Collections.addAll 风格

JDK 使用下界来表示接收端。例如,一个将元素推入目标集合的泛型方法。

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

public class Main {
    @SafeVarargs
    static <T> void addAll(List<? super T> dest, T... items) {
        for (T item : items) dest.add(item);
    }
    public static void main(String[] args) {
        List<Object> sink = new ArrayList<>();
        addAll(sink, "a", "b", "c");
        System.out.println(sink);
    }
}

使用 super 的比较器

一个实际用例是:Comparator<? super T> 允许排序方法接受一个为超类型编写的比较器。

一个可以通过 toString 比较任意对象的比较器,也能对 String 列表进行排序。

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

public class Main {
    public static void main(String[] args) {
        Comparator<Object> byString = Comparator.comparing(Object::toString);
        List<String> names = new ArrayList<>(List.of("Charlie", "Alice", "Bob"));
        names.sort(byString); // List<String>.sort accepts Comparator<? super String>
        System.out.println(names);
    }
}

上界与下界互为镜像

这两个通配符正好相反:

  • ? extends T:读取 T,无法写入。它是生产者。
  • ? super T:写入 T,只能读取 Object。它是消费者。

这种对称性正是 PECS 规则的核心,您将在下一节学习该规则。

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

public class Main {
    public static void main(String[] args) {
        List<? super Integer> consumer = new ArrayList<Number>();
        consumer.add(1);
        System.out.println("consumer can write: " + consumer);
    }
}

在不同界限之间复制

复制方法可以同时展示两种通配符:从 ? extends T 读取,写入 ? super T。

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

public class Main {
    static <T> void copy(List<? extends T> src, List<? super T> dest) {
        for (T t : src) dest.add(t);
    }
    public static void main(String[] args) {
        List<Integer> src = List.of(1, 2, 3);
        List<Number> dest = new ArrayList<>();
        copy(src, dest);
        System.out.println(dest);
    }
}

何时不应使用通配符

如果一个方法既读取又写入 T,请不要使用通配符。请使用确切类型或命名的类型参数。

通配符正是为只读或只写的场景而存在的。

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

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

快速检查

测试您对下界的理解。

回顾

您已经学习了下界通配符:

  • ? super T 表示 T 的某个未知超类型。
  • 您可以写入 T 及其子类型(消费者)。
  • 读取结果只能是 Object。
  • 它与 ? extends T 完全相互对应。

接下来是将两者联系起来的规则:PECS。

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

public class Main {
    public static void main(String[] args) {
        List<? super Integer> list = new ArrayList<Number>();
        list.add(42);
        System.out.println("Lower bound recap: " + list);
    }
}

常见问题解答

「下界通配符」课时是免费的吗?

是的 — 「下界通配符」的完整文本可在网页上免费阅读。要进行交互式练习(内置代码编辑器和全天候 AI 导师)并解锁 Java Academy 课程的其余内容,请升级到 CoddyKit PRO。 Java Academy 课程共包含 4 节课。

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

面向消费者的 ? super T 你通过在浏览器中直接运行的动手代码来练习 Java Academy,全天候 AI 导师会在你学习这节课的过程中回答你的问题。

学习 Java Academy 需要有经验吗?

无需任何先前经验。CoddyKit 上的 Java Academy 课程适合初学者到高级学习者,你可以从这里开始或从头开始,按照自己的节奏学习。 这是第 2 节课,共 4 节。

「下界通配符」课时需要多长时间?

大多数 CoddyKit 课程大约需要 5–10 分钟。每节课都很精短且互动,所以你能稳步进步,并在网页和应用中从离开的地方继续。

我能在这节 Java Academy 课中编写并运行代码吗?

能。每节 Java Academy 课都包含内置代码编辑器,你可以在浏览器中直接编写并运行真实代码,并获得即时 AI 反馈 — 无需本地设置。

此课程中的所有课时

  1. 上界通配符
  2. 下界通配符
  3. PECS 原则
  4. API 中的通配符
← 返回 Java Academy