请比较两段代码:
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>
问题是,您实际上没有覆盖Parent
的m(T)
方法。因此,您有两个独立的函数可用:
-
Parent.m(String s)
,因为您强制在Parent中的T为String -
Child.m(T)
想象一下Child的声明:
class Child<T extends CharSequence> extends Parent<Integer>
现在,Parent<T>
得到Parent<Integer>
,这意味着Parent
的m
方法现在是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
。没有歧义。