核心概念:<T extends SomeClass>
在 Java 泛型中,<T extends SomeClass> 这种语法被称为上界限定,它的含义是:

“泛型类型 T 必须是 SomeClass 本身,或者是 SomeClass 的任何子类。”
这里有几个关键点需要特别注意:
-
extends的双重含义:- 对于类,
extends表示“继承”或“扩展”。 - 对于接口,
extends表示“实现”。<T extends Runnable>的意思是T必须是Runnable接口的实现类。
- 对于类,
-
extends,而不是super:
(图片来源网络,侵删)- 在泛型通配符中,有
? extends T(上界通配符)和? super T(下界通配符)。 - 在泛型类型参数定义中,我们只使用
extends,它同时涵盖了“继承类”和“实现接口”两种情况,Java 设计者没有选择使用implements关键字,是为了保持语法的简洁性。
- 在泛型通配符中,有
为什么需要 extends?—— 主要用途
使用 extends 的主要目的是为了增加泛型的约束,从而可以在泛型方法或类内部调用上界类/接口中定义的方法。
场景举例:计算一个列表中所有元素的最大值
假设我们想写一个泛型方法,可以找出一个 List 中最大的元素,一个直观的想法可能是这样:
// 错误的尝试!
public static <T> T max(List<T> list) {
if (list == null || list.isEmpty()) {
throw new IllegalArgumentException("List must not be empty");
}
T maxElement = list.get(0);
for (T t : list) {
// 编译错误!: bad operand types for binary operator '>'
// first type: T
// second type: T
if (t > maxElement) { // 问题在这里!
maxElement = t;
}
}
return maxElement;
}
为什么会编译错误?
编译器在编译时只知道 T 是一个类型,但完全不知道 T 是否支持 > 比较运算符。T 可能是 String,也可能是 Integer,也可能是你自己定义的 Person 类。Person 类之间是不能用 > 比较的。
解决方案:使用 extends
我们可以告诉编译器:“我保证这个方法被调用时,传入的 T 类型一定是支持比较的”,在 Java 中,支持比较的接口是 Comparable。
import java.util.List;
// 正确的泛型方法
public static <T extends Comparable<T>> T max(List<T> list) {
if (list == null || list.isEmpty()) {
throw new IllegalArgumentException("List must not be empty");
}
T maxElement = list.get(0);
for (T t : list) {
// 现在编译器知道 T 一定有 compareTo 方法
if (t.compareTo(maxElement) > 0) {
maxElement = t;
}
}
return maxElement;
}
代码解析:
<T extends Comparable<T>>:这里我们声明了一个类型参数T。extends Comparable<T>:我们给T加了一个约束,T必须实现Comparable<T>接口。t.compareTo(maxElement):因为编译器已经知道t是Comparable的实例,所以我们可以安全地调用compareTo方法。
这个方法就可以正确处理 List<Integer>、List<String>、List<BigDecimal> 等任何实现了 Comparable 接口的类型的列表了。
List<Integer> intList = List.of(1, 5, 3, 9, 2);
Integer maxInt = max(intList); // 正确
List<String> strList = List.of("apple", "banana", "pear");
String maxStr = max(strList); // 正确,String 实现了 Comparable
// List<Person> personList = ...; // Person 类没有实现 Comparable,这里编译就会报错!
// Person maxPerson = max(personList);
extends 的规则与限制
一旦你为泛型类型参数设置了上界,就必须遵守它的规则。
规则 1:只能调用上界类/接口中声明的方法
class Animal {
public void eat() {
System.out.println("Animal is eating.");
}
}
class Dog extends Animal {
public void bark() {
System.out.println("Dog is barking.");
}
}
// 一个处理 Animal 列表的工具类
class AnimalProcessor {
// 这个方法可以处理任何 Animal 或其子类
public static void process(List<? extends Animal> animals) {
for (Animal a : animals) {
a.eat(); // 正确!因为所有 Animal 都有 eat() 方法
// a.bark(); // 编译错误!编译器只知道 a 是 Animal 类型,不知道它是 Dog
}
}
}
规则 2:不能向列表中添加元素(“Producer Extends”原则)
这是 PECS(Producer Extends, Consumer Super)原则的一部分,当你使用 ? extends T 作为方法参数时,表示你从这个列表中读取(生产)元素,但你不能向其中添加(消费)元素(除了 null)。
List<Dog> dogs = new ArrayList<>(); dogs.add(new Dog()); // List<? extends Animal> animalList = dogs; // 这是允许的,赋值成功 // animalList.add(new Dog()); // 编译错误! // animalList.add(new Animal()); // 编译错误! // 为什么? // animalList 的类型是 List<? extends Animal>。 // 它可能指向一个 List<Dog>,也可能指向一个 List<Cat>。 // 如果我们允许 animalList.add(new Dog()),那么当它实际指向 List<Cat> 时,岂不是把一只狗放进猫的列表里了?这破坏了类型安全。 // 为了安全起见,编译器禁止了所有向 ? extends T 列表添加元素的操作(除了 null)。
extends 与通配符 ? extends T 的关系
你可能会注意到泛型方法和通配符中都有 extends,它们非常相似,但使用场景略有不同。
-
<T extends SomeClass>:用于定义泛型类或泛型方法的类型参数,它是在声明一个具体的类型T,并对其施加约束。class Box<T extends Number> { ... }或public <T extends Number> void method(T t) { ... }
-
List<? extends SomeClass>:用于声明一个变量或方法的参数,它表示一个“未知的、但一定是SomeClass或其子类”的列表类型,它不是一个具体的类型T,而是一个通配符。public void process(List<? extends Number> list) { ... }
一个简单的类比:
<T extends Number>:就像在说“我要创建一个盒子,这个盒子能装任何Number类型的东西,Integer,Double”,你在定义规则。List<? extends Number>:就像在说“给我一个能装Number或其子类的列表,但我现在不关心它具体是List<Integer>还是List<Double>”,你在使用一个已存在的、符合规则的列表。
多重上界
Java 泛型还支持一个类型参数同时继承一个类并实现多个接口。
语法规则:
- 类名必须放在
extends关键字之后,并且只能有一个类。 - 接口可以有多个,用
&符号连接。 - 类必须在接口之前。
示例:
class MyClass {}
interface InterfaceA {}
interface InterfaceB {}
// T 必须是 MyClass 的子类,并且同时实现 InterfaceA 和 InterfaceB
public class MyClass<T extends MyClass & InterfaceA & InterfaceB> {
// ...
}
// 泛型方法示例
public static <T extends Comparable<T> & Serializable> void doSomething(T t) {
// t 既有 compareTo 方法,又有 Serializable 的特性
System.out.println(t);
}
| 特性 | 描述 |
|---|---|
| 核心作用 | 为泛型类型参数 T 设置上界,即 T 必须是某个类或接口的子类/实现类。 |
| 语法 | <T extends UpperBound> |
extends 的含义 |
对于类,表示“继承”;对于接口,表示“实现”。 |
| 主要用途 | 方法调用:允许在泛型方法/类内部调用上界类/接口中定义的方法。 增加约束:提高代码的类型安全性,明确泛型类型的 capabilities。 |
| 关键限制 | 只能调用上界方法:不能使用子类特有的方法。 不能添加元素:对于 ? extends T 形式的列表,是“生产者”,只能读,不能写(null除外)。 |
| 多重上界 | 一个类型参数可以继承一个类并实现多个接口,用 & 连接,且类必须在接口前。 |
理解 extends 是掌握 Java 泛型高级特性的基石,它让你能够编写出更通用、更健壮、更类型安全的代码。
