下界通配符
面向消费者的 ? 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 反馈 — 无需本地设置。