java方法重写禁止参数协变,因违反里氏替换原则;参数类型必须保持不变(擦除后签名一致),仅返回类型支持协变。

Java中泛型类里的方法重写时,**不允许引入协变参数类型**——也就是说,子类不能把父类方法的参数类型“收窄”为更具体的子类型,否则编译直接失败。
参数类型必须保持不变或拓宽(实际要求是“不可收窄”)
Java 方法重写对参数类型的约束本质是:子类方法必须能接受父类方法所声明的**所有合法参数实例**。如果父类方法声明参数为 A>,那它应能接收任意 A 的子类对象(比如 B、C extends A
- ❌ 错误写法:
public B someFunction(B obj)重写public A<t> someFunction(A> obj)</t> - ✅ 正确写法:
public B someFunction(A> obj)—— 参数类型未变,返回类型可协变为 B
泛型擦除后签名必须一致
Java 的方法重写不看泛型类型参数本身,而看**类型擦除后的签名**。例如:
在 Java 中初始化和管理阿里云 SDK客户端。包括单例模式、线程安全、endpoint 与 region 配置、VPC 终端节点、同步与异步等。
- 父类方法:
<t> List<t> getData()</t></t>→ 擦除后是List getData() - 子类若写成:
ArrayList<string> getData()</string>→ 擦除后是ArrayList getData(),与父类不匹配,编译报错 - 但写成:
ArrayList<string> getData()</string>重写List<string> getData()</string>是合法的,因为ArrayList是List的子类(协变返回),且擦除后都是List
通配符不参与重写签名匹配
?、? extends T、? super T 这些通配符只用于变量声明、方法调用或集合操作(体现 PECS 原则),它们**不出现在重写签名的判定逻辑中**。重写是否成立,只取决于方法名、擦除后的参数类型、返回类型(支持协变)和访问修饰符。
- 你不能靠改写
A>为A<integer></integer>来“重写”——擦除后都是A,看似相同,但A<integer></integer>并非A>的子类型,也不满足参数兼容性;编译器会拒绝 - 真正起作用的是参数类型的继承关系,不是泛型实参的精确匹配
为什么不允许参数协变?违反 Liskov 替换原则
里氏替换原则要求:任何父类出现的地方,子类都能安全替代。如果父类方法承诺“我能处理任意 A>”,而子类悄悄变成“只处理 B”,那么外部代码传入 C extends A
- 参数位置需要的是逆变语义(理论上应允许更宽泛类型),但 Java 实际只允许完全一致(即不变);它不支持像 C# 那样的参数逆变重写
- 所以“拓宽参数类型”(如把
A>改成Object)也不行——那属于重载,不是重写,且会因签名不匹配被拒
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南










