详解泛型通配符在方法重写中的协变与逆变规则

雨萱同学_4063

雨萱同学_4063

2026-04-07

528人浏览

原创

泛型通配符不参与方法重写签名匹配,重写仅取决于方法名、擦除后的参数类型、协变返回类型和访问修饰符;java支持返回类型协变但不支持参数类型逆变,通配符仅用于使用侧(如pecs)而非重写侧。

详解泛型通配符在方法重写中的协变与逆变规则

泛型通配符本身不参与方法重写(override)的签名匹配,Java 中方法是否构成重写,只看**方法名、参数类型(擦除后)、返回类型(协变允许)和访问修饰符**。通配符是编译期类型系统工具,用于增强集合等结构的类型安全使用,它不改变方法在字节码层面的签名,因此不会直接“触发”或“驱动”重写规则。真正起作用的是:返回类型的协变(covariant return type)和参数类型的逆变(contravariant parameter type)——但后者在 Java 中**不被支持**,仅存在于理论或部分语言(如 C#)中。

一、返回类型可协变:重写时允许更具体的子类型

这是 Java 自 5.0 起明确支持的规则。父类方法返回父类型,子类重写时可返回其子类型,无需通配符参与。

  • 合法示例:
    class Animal { }
    class Dog extends Animal { }

    class Repository {
      Animal get() { return new Animal(); }
    }

    class DogRepository extends Repository {
      @Override
      Dog get() { return new Dog(); } // ✅ 合法:Dog 是 Animal 的子类
    }
  • 注意:这与 ? extends Animal 无关。通配符常用于变量声明或参数类型(如 List extends Animal>),但不用于重写方法的返回类型声明本身——因为返回类型必须是具体、可确定的类或接口。

二、参数类型不允许逆变:Java 不支持“更宽泛”的参数重写

Java 方法重写要求子类方法的参数列表必须与父类**完全一致(擦除后)**。你不能把 void save(Animal a) 重写为 void save(Object o),哪怕 Object 是 Animal 的父类。这本质上是拒绝参数位置的逆变。

  • 非法示例:
    class Repository {
      void save(Animal a) { }
    }

    class DogRepository extends Repository {
      @Override
      void save(Object o) { ... } // ❌ 编译错误:不是重写,是重载(overload)
    }
  • 为什么?因为调用方按父类契约传入 Animal 实例,若子类接受更宽泛的 Object,看似安全;但实际破坏了里氏替换原则(LSP)的语义约束——子类行为必须严格遵循父类定义的输入边界,否则多态调用可能意外接受非法输入(比如传入 String)。

三、通配符常见于方法参数,但属于“使用侧”,非“重写侧”

虽然通配符不出现在重写签名中,但它高频出现在被重写或被调用的方法参数里,体现 PECS 原则:

  • void addAll(Collection super T> c) —— 消费者模式,允许传入 T 或其任意父类的集合(如 Collection<object></object> 接收 Collection<string></string>);
  • T getFirst(List extends T> list) —— 生产者模式,允许传入 T 或其任意子类的列表(如 List<integer></integer> 传给 List extends Number>)。
  • 这些通配符是方法**声明时对调用方的约束**,不是重写机制的一部分。子类重写该方法时,参数仍需保持相同签名(即同样用 ? super T 或 ? extends T),不能擅自改成具体类型或换界。

四、真实重写场景中的泛型一致性要求

当父类方法含泛型参数(如泛型类中的方法),子类重写时必须保持类型变量含义不变:

  • 父类:<t> void process(T item)</t> → 子类重写仍须是 <t> void process(T item)</t>,不能变成 <u> void process(U item)</u> 或 void process(Object item);
  • 若父类是泛型类 Repository<t></t>,其中 T getById(int id),子类 DogRepository extends Repository<dog></dog> 重写时返回类型可协变为 Dog(见第一点),但参数仍为 int,不可改动。
PHP速学视频免费教程(入门到精通)
PHP速学视频免费教程(入门到精通)

PHP怎么学习?PHP怎么入门?PHP在哪学?PHP怎么学才快?不用担心,这里为大家提供了PHP速学教程(入门到精通),有需要的小伙伴保存下载就能学习啦!

下载

相关标签:

本站声明:本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn

相关专题

更多
string转int
string转int

在编程中,我们经常会遇到需要将字符串(str)转换为整数(int)的情况。这可能是因为我们需要对字符串进行数值计算,或者需要将用户输入的字符串转换为整数进行处理。php中文网给大家带来了相关的教程以及文章,欢迎大家前来学习阅读。

2023.08.02

5539

3

java多态详细介绍
java多态详细介绍

本专题整合了java多态相关内容,阅读专题下面的文章了解更多详细内容。

2025.11.27

315

14

java多态详细介绍
java多态详细介绍

本专题整合了java多态相关内容,阅读专题下面的文章了解更多详细内容。

2025.11.27

315

14

java多态详细介绍
java多态详细介绍

本专题整合了java多态相关内容,阅读专题下面的文章了解更多详细内容。

2025.11.27

315

14

string转int
string转int

在编程中,我们经常会遇到需要将字符串(str)转换为整数(int)的情况。这可能是因为我们需要对字符串进行数值计算,或者需要将用户输入的字符串转换为整数进行处理。php中文网给大家带来了相关的教程以及文章,欢迎大家前来学习阅读。

2023.08.02

5539

3

int占多少字节
int占多少字节

int占4个字节,意味着一个int变量可以存储范围在-2,147,483,648到2,147,483,647之间的整数值,在某些情况下也可能是2个字节或8个字节,int是一种常用的数据类型,用于表示整数,需要根据具体情况选择合适的数据类型,以确保程序的正确性和性能。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

2024.08.29

2785

6

c++怎么把double转成int
c++怎么把double转成int

本专题整合了 c++ double相关教程,阅读专题下面的文章了解更多详细内容。

2025.08.29

3448

10

C++中int的含义
C++中int的含义

本专题整合了C++中int相关内容,阅读专题下面的文章了解更多详细内容。

2025.08.29

2465

10

javascriptvoid(o)怎么解决
javascriptvoid(o)怎么解决

javascriptvoid(o)的解决办法:1、检查语法错误;2、确保正确的执行环境;3、检查其他代码的冲突;4、使用事件委托;5、使用其他绑定方式;6、检查外部资源等等。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

2023.11.23

636

5

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
热门推荐
/
最新课程
phpStudy极速入门视频教程
phpStudy极速入门视频教程

共6课时 | 54.6万人学习

独孤九贱(4)_PHP视频教程
独孤九贱(4)_PHP视频教程

共89课时 | 133.4万人学习