0Pricing
Java Academy · 课时

上界通配符

面向生产者的 ? 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 反馈 — 无需本地设置。

此课程中的所有课时

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