Java重写方法时使用参数子类型为形参的报错解决方案
问题原因说明
Java的方法重写有明确的语法约束:重写方法的参数列表(类型擦除后)必须和父类/接口中被重写方法的参数列表完全一致,你把方法参数从父类定义的V<T>换成子类型V1<T>,本质已经改变了方法签名,这不是重写而是重载,加@Override注解时编译器检查到父类没有匹配签名的方法,自然会报错。
你尝试修改的方法级泛型版本也无法生效,原因是方法上声明的泛型参数<T, U extends V<T>>的实际类型是由方法调用方决定的,接口契约要求实现类的f方法必须能接收所有符合U extends V<T>约束的类型参数。如果实现类只接受V1<T>,相当于缩小了方法的参数接收范围,违反里氏替换原则:按照接口A的约定,调用方可以传入任意V<T>的实现类实例,而你的B类实现无法处理非V1类型的参数,直接破坏了接口的契约约定。
可行实现方案
如果要实现「实现类只接收特定V子类型作为参数」的需求,需要把参数类型的泛型声明从方法层面提升到接口层面,让实现类在实现接口时就明确指定参数的具体子类型,这种写法完全符合重写规则,也不违反多态约束:
interface A<T, U extends V<T>> { T f(U v); } interface V<T> { T v(); } interface V1<T> extends V<T> { T v1(); } class B<T> implements A<T, V1<T>> { @Override public T f(V1<T> v) { return v.v1(); } }
这种实现下,B类的f方法参数固定为V1<T>,编译器不会报错,也符合重写的要求。因为B实现A接口时已经明确将泛型参数U指定为V1<T>,对应接口实例的方法契约本身就要求传入V1类型参数,不存在契约破坏的问题。
如果你不想修改接口的泛型定义,也可以在重写方法内部做运行时类型校验,但这种方式不推荐,因为它破坏了原接口的参数约定,调用方传入非V1类型的V实现时会抛出运行时异常:
class B implements A { @Override public <T> T f(V<T> v) { if (!(v instanceof V1<T>)) { throw new IllegalArgumentException("参数类型必须为V1的实现类"); } return ((V1<T>) v).v1(); } }
内容的提问来源于stack exchange,提问作者Wickoo
相关产品推荐
相关产品推荐

