杰瑞科技汇

Java泛型中extends关键字到底有何作用?

核心概念:<T extends SomeClass>

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

Java泛型中extends关键字到底有何作用?-图1
(图片来源网络,侵删)

“泛型类型 T 必须是 SomeClass 本身,或者是 SomeClass 的任何子类。”

这里有几个关键点需要特别注意:

  1. extends 的双重含义

    • 对于extends 表示“继承”或“扩展”。
    • 对于接口extends 表示“实现”。<T extends Runnable> 的意思是 T 必须是 Runnable 接口的实现类。
  2. extends,而不是 super

    Java泛型中extends关键字到底有何作用?-图2
    (图片来源网络,侵删)
    • 在泛型通配符中,有 ? 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):因为编译器已经知道 tComparable 的实例,所以我们可以安全地调用 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 泛型高级特性的基石,它让你能够编写出更通用、更健壮、更类型安全的代码。

分享:
扫描分享到社交APP
上一篇
下一篇