上界通配符
面向生产者的 ? extends T
上界通配符 是 CoddyKit 上的免费 Java Academy 课时。 这是第 1 节课,共 4 节。 你可以在下方免费阅读本课时的完整内容 — 然后在浏览器中使用内置代码编辑器和全天候 AI 导师进行实践。 这是 Java Academy 学习路径的一部分,你的进度在网页和 CoddyKit 应用中同步。 Java Academy 课程共包含 4 节课。
泛型是不变的
在 Java 中,泛型具有不变性。List<Integer> 不是 List<Number>,即使整数类型属于数值类型。
这会让初学者感到意外,但它能保持类型系统的健全性。通配符可以安全地放宽这一限制。
import java.util.List;
public class Main {
public static void main(String[] args) {
List<Integer> ints = List.of(1, 2, 3);
// List<Number> nums = ints; // would NOT compile
System.out.println("List<Integer> is not List<Number>");
System.out.println(ints);
}
}上界通配符
? extends T 是上界通配符。它表示“某个未知类型,该类型是 T 或 T 的子类型”。
List<? extends Number> 可以指向 List<Integer>、List<Double> 或 List<Number>。
import java.util.List;
public class Main {
public static void main(String[] args) {
List<? extends Number> a = List.of(1, 2, 3);
List<? extends Number> b = List.of(1.5, 2.5);
System.out.println(a.get(0));
System.out.println(b.get(0));
}
}从生产者读取
使用 ? extends Number 时,您可以安全地将元素作为 Number 读取。
无论实际元素类型是什么,都能保证它至少属于数值类型,因此读取是类型安全的。
import java.util.List;
public class Main {
static double sum(List<? extends Number> list) {
double total = 0;
for (Number n : list) total += n.doubleValue();
return total;
}
public static void main(String[] args) {
System.out.println(sum(List.of(1, 2, 3)));
System.out.println(sum(List.of(1.5, 2.5)));
}
}无法向其中写入
关键在于:您无法添加元素到 ? extends Number 列表中(null 除外)。
编译器不知道确切的元素类型。它可能是 List<Integer>,因此添加 Double 会不安全。
import java.util.ArrayList;
import java.util.List;
public class Main {
public static void main(String[] args) {
List<? extends Number> list = new ArrayList<Integer>();
// list.add(1); // does NOT compile
// list.add(1.5); // does NOT compile
System.out.println("Cannot add to ? extends Number");
System.out.println("size = " + list.size());
}
}为什么禁止写入
想象一下如果允许这样做。您可以将 List<Integer> 作为 List<? extends Number> 传入,然后添加一个 Double。
之后,按照 List<Integer> 读取列表的代码就会得到 ClassCastException。编译器通过禁止写入来避免这种情况。
import java.util.List;
public class Main {
public static void main(String[] args) {
// Conceptual: this is why add is blocked
System.out.println("Writing a Double into a List<Integer> would corrupt it");
System.out.println("so ? extends bans all adds");
}
}生产者用例
当一个方法只为您产生(读出)值时,请使用 ? extends T。
将元素从源集合复制出来的方法就是一个典型例子。
import java.util.ArrayList;
import java.util.List;
public class Main {
static List<Number> copyOut(List<? extends Number> source) {
List<Number> dest = new ArrayList<>();
for (Number n : source) dest.add(n);
return dest;
}
public static void main(String[] args) {
System.out.println(copyOut(List.of(1, 2, 3)));
}
}有界类型参数与通配符
不要混淆 <T extends Number>(有界类型参数)和 ? extends Number(通配符)。
- 类型参数:为类型命名,以便在整个方法中使用。
- 通配符:匿名的未知子类型,不需要为它命名时使用。
import java.util.List;
public class Main {
static <T extends Number> T first(List<T> list) { return list.get(0); }
public static void main(String[] args) {
Integer i = first(List.of(10, 20));
System.out.println(i);
}
}多重上界
类型参数可以使用 & 指定多个上界,例如 <T extends Number & Comparable<T>>。
通配符只支持一个上界,因此指定多个上界需要使用命名的类型参数。
import java.util.List;
public class Main {
static <T extends Number & Comparable<T>> T max(List<T> list) {
T best = list.get(0);
for (T t : list) if (t.compareTo(best) > 0) best = t;
return best;
}
public static void main(String[] args) {
System.out.println(max(List.of(3, 9, 1, 7)));
}
}使用 Collections.max 的通配符
JDK 广泛使用上界。例如,对只读集合求和或进行扫描。
这里,我们从一个声明为生产者的列表中找出最大值。
import java.util.List;
public class Main {
static double largest(List<? extends Number> nums) {
double max = Double.NEGATIVE_INFINITY;
for (Number n : nums) max = Math.max(max, n.doubleValue());
return max;
}
public static void main(String[] args) {
System.out.println(largest(List.of(4, 2, 9, 1)));
System.out.println(largest(List.of(0.5, 9.9, 3.3)));
}
}空值是唯一可写入的值
能够添加到 ? extends T 集合中的唯一值是 null,因为空值可以赋给任何引用类型。
这很少有用,但它解释了为什么 add 并非完全被禁止,只是在实际使用中基本不可行。
import java.util.ArrayList;
import java.util.List;
public class Main {
public static void main(String[] args) {
List<? extends Number> list = new ArrayList<Number>();
// Only null is assignable; demonstrated conceptually
System.out.println("Only null could be added; we avoid that");
System.out.println("size = " + list.size());
}
}始终可以将读出值视为对象
由于每种类型都扩展自 Object,因此您始终可以将任何通配符列表中的元素读取为 Object。
如果上界是 Number,您还可以直接将它们读取为 Number,这是额外的好处。
import java.util.List;
public class Main {
public static void main(String[] args) {
List<? extends Number> nums = List.of(1, 2.0, 3L);
for (Object o : nums) System.out.println(o.getClass().getSimpleName());
}
}快速检查
测试您对上界的理解。
回顾
您已经学习了上界通配符:
? extends T表示 T 的某个未知子类型。- 您可以将元素读取为 T(生产者)。
- 您无法添加非空元素。
- 泛型具有不变性,而通配符可以安全地放宽这种限制。
接下来是它的镜像:下界通配符。
import java.util.List;
public class Main {
public static void main(String[] args) {
List<? extends Number> nums = List.of(1, 2, 3);
System.out.println("Upper bound recap: read " + nums.get(0));
}
}常见问题解答
「上界通配符」课时是免费的吗?
是的 — 「上界通配符」的完整文本可在网页上免费阅读。要进行交互式练习(内置代码编辑器和全天候 AI 导师)并解锁 Java Academy 课程的其余内容,请升级到 CoddyKit PRO。 Java Academy 课程共包含 4 节课。
「上界通配符」这节课中我会学到什么?
面向生产者的 ? extends T 你通过在浏览器中直接运行的动手代码来练习 Java Academy,全天候 AI 导师会在你学习这节课的过程中回答你的问题。
学习 Java Academy 需要有经验吗?
无需任何先前经验。CoddyKit 上的 Java Academy 课程适合初学者到高级学习者,你可以从这里开始或从头开始,按照自己的节奏学习。 这是第 1 节课,共 4 节。
「上界通配符」课时需要多长时间?
大多数 CoddyKit 课程大约需要 5–10 分钟。每节课都很精短且互动,所以你能稳步进步,并在网页和应用中从离开的地方继续。
我能在这节 Java Academy 课中编写并运行代码吗?
能。每节 Java Academy 课都包含内置代码编辑器,你可以在浏览器中直接编写并运行真实代码,并获得即时 AI 反馈 — 无需本地设置。