Java泛型继承.重载或重写



请比较两段代码:

snippet1:

class Ideone
{
    public static void main (String[] args) throws java.lang.Exception
    {
        Child<String> p =new Child<String>();
        p.m("1");
    }
}
class Parent <T>{
    void m(T t){
    }
} 
class Child<T extends CharSequence> extends Parent<String>{
    void m(T t){
    }
} 

结果(在线编译器):

Main.java:13: error: reference to m is ambiguous, both method m(t# 1)在父类和方法m(t# 2)在子类匹配下午("1");^,t# 1、t# 2是类型变量:t# 1扩展在类Parent中声明的Objectt# 2扩展了在类Child

中声明的CharSequence

snippet2(只有一个变化!!):

class Ideone
{
    public static void main (String[] args) throws java.lang.Exception
    {
        Parent<String> p =new Child<String>();
        p.m("1");
    }
}
class Parent <T>{
    void m(T t){
    }
} 
class Child<T extends CharSequence> extends Parent<String>{
    void m(T t){
    }
} 

这段代码编译得很好!(在线编译器)

所以这里的事情是你所做的实际上是不是重写。注意,如果您将@Override注释添加到Child中的m(),那么在这两种情况下都将得到编译错误:

Main.java:22: error: method does not override or implement a method from a supertype
    @Override

你可以想象Child实际上是这样的:

class Child<T extends CharSequence> extends Parent<String>{
    void m(T t){
    }
    void m(String t) { // <-- This was the method inherited from Parent
    }
} 

所以当你尝试

Child<String> p =new Child<String>();

编译器看到引用类型Child<String>,并且看到你实际上有两个方法可以工作,因为你有m(String t),继承自Parent<String>m(T t),在Child中定义,其中T == String。因为现在你有两个有效的m(String t)方法,你将有一个模糊的调用。

如果你有

Parent<String> p =new Child<String>();

编译器看到引用类型Parent<String>,并使用它来解析对m(String)的调用。由于Parent<String>只定义了一个这样的方法,因此没有歧义的方法调用,因此代码可以编译。

这就是为什么@Override注释应该始终使用的原因之一——对于是否重载或重写方法没有混淆。


有趣的事实:如果内存可用,这实际上是泛型被而不是擦除的少数情况之一。如果你反编译Child,你会看到头文件
class Child<T extends java.lang.CharSequence> extends Parent<java.lang.String> 

问题是,您实际上没有覆盖Parentm(T)方法。因此,您有两个独立的函数可用:

  • Parent.m(String s),因为您强制在Parent中的T为String
  • Child.m(T)

想象一下Child的声明:

class Child<T extends CharSequence> extends Parent<Integer>

现在,Parent<T>得到Parent<Integer>,这意味着Parentm方法现在是m(Integer i)方法,而您在子类中仍然有一个m(T)方法可用。现在区别很明显了。

您选择T作为String,它碰巧扩展了CharSequence,但这并没有使它覆盖Parent的m(T)方法。所以,把你的签名改成:

class Child<T extends CharSequence> extends Parent<T>

应该工作。现在你重写了Parent在Child中的方法

您的Child类通过声明具有依赖于有界类型的参数的方法来绕过重写规则(并进入重载领域)。该类型可以是String,也可以不是,因此在编译这两个类型时,不会与父类中声明的方法发生冲突。

然而,当编译Ideone类时,通过在 中将类型参数声明为String
Child<String> p = new Child<String>();

类型String被绑定到Child声明并在Child#m(..)中使用的类型参数T。因此该方法显示为

void m(String t) {}

但是Parent#m(..)也是如此,因为Child类声明中的类型参数String

class Child<T extends CharSequence> extends Parent<String> {
因此,Child类有两个m(String)方法用于该调用。这个调用是模棱两可的。

Parent<String> p =new Child<String>();
p.m("1");

Child类方法是不可见的,因为你的引用类型是Parent。没有歧义。

最新更新