泛型通配符不参与方法重写签名匹配,重写仅取决于方法名、擦除后的参数类型、协变返回类型和访问修饰符;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,不可改动。







