如果我有这样的基类,我就无法改变:
public abstract class A {
public abstract Object get(int i);
}
我尝试使用类B
来扩展它:
public class B extends A{
@Override
public String get(int i){
//impl
return "SomeString";
}
}
一切都好。但是如果我尝试的话,我试图让它更通用的尝试失败了:
public class C extends A{
@Override
public <T extends Object> T get(int i){
//impl
return (T)someObj;
}
}
我想不出有什么理由不应该被禁止。根据我的理解,泛型类型T
绑定到Object
- 这是A
的请求返回类型。如果我可以在String
中添加AnyObject
或B
作为我的返回类型,为什么我不允许将<T extends Object> T
放入C
类中?
从我的观点来看,另一种奇怪的行为是另外一种方法:
public class D extends A{
@Override
public Object get(int i){
//impl
}
public <T extends Object> T get(int i){
//impl
}
}
也是不允许的,提供了DuplicateMethod
的提示。这个,至少让我困惑,我认为Java应该做出决定:如果它是 相同的 返回类型,为什么不允许覆盖;如果不是,为什么我不能添加这种方法?告诉我它是相同的,但不允许它被覆盖,非常奇怪,基于常识。
答案 0 :(得分:10)
方法m1的签名是方法m2的签名的子签名,如果:
m2与m1具有相同的签名,或
m1的签名与删除(§4.6)相同 签名为m2。
按照上述规则,因为您的父母没有擦除而您的孩子有一个擦除,所以它不是有效的覆盖。
JLS#8.4.8.3. Requirements in Overriding and Hiding
例8.4.8.3-4。擦除影响覆盖
一个类不能有两个具有相同名称和类型擦除的成员方法:
class C<T> {
T id (T x) {...}
}
class D extends C<String> {
Object id(Object x) {...}
}
这是非法的,因为D.id(Object)是D的成员,C.id(String)以D的超类型声明,并且:
类的两种不同方法可能不会覆盖具有相同擦除的方法:
class C<T> {
T id(T x) {...}
}
interface I<T> {
T id(T x);
}
class D extends C<String> implements I<Integer> {
public String id(String x) {...}
public Integer id(Integer x) {...}
}
这也是非法的,因为D.id(String)是D的成员,D.id(Integer)在D中声明,并且:
此外,它给出了允许从超级到孩子的情况的例子
子签名的概念旨在表达两种方法之间的关系,这两种方法的签名不相同,但其中一种方法可以覆盖另一种方法。具体来说,它允许其签名不使用泛型类型的方法覆盖该方法的任何泛化版本。这很重要,因此库设计人员可以独立于定义库的子类或子接口的客户端自由地生成方法。
考虑一下这个例子:
class CollectionConverter {
List toList(Collection c) {...}
}
class Overrider extends CollectionConverter {
List toList(Collection c) {...}
}
现在,假设此代码是在引入泛型之前编写的,现在CollectionConverter类的作者决定生成代码,因此:
class CollectionConverter {
<T> List<T> toList(Collection<T> c) {...}
}
无需特殊分配,Overrider.toList将不再覆盖CollectionConverter.toList。相反,代码是非法的。这将极大地抑制泛型的使用,因为库编写者会犹豫是否迁移现有代码。
答案 1 :(得分:5)
嗯,对于第一部分,答案是Java不允许泛型方法覆盖非泛型方法,即使擦除是相同的。这意味着,即使您只使用覆盖方法,它也不会起作用:
public <T extends Object> Object get(int i)
我不知道为什么Java会出现这种限制(考虑一下),我只是认为它与为泛型类进行子类化而实现的特殊情况有关。
您的第二个定义基本上会转换为:
public class D extends A{
@Override
public Object get(int i){
//impl
}
public Object get(int i){
//impl
}
}
这显然是一个问题。
答案 2 :(得分:2)
来自JLS部分8.4.8.3 Overriding and hiding:
如果类型声明T具有成员方法m1并且存在以T声明的方法m2或T的超类型使得以下所有条件成立,那么这是编译时错误:
m1和m2具有相同的名称。
- 访问
m2可从T。
m1的签名不是m2签名的子签名(§8.4.2)。
- 醇>
m1的签名或某些方法m1覆盖(直接或间接)具有与m2的签名相同的擦除或某种方法m2覆盖(直接或间接)。
1和2持有。
3也有,因为(引自JLS section 8.4.2):
子签名的概念旨在表达两种方法之间的关系,这两种方法的签名不相同,但其中一种方法可以覆盖另一种方法。 具体来说,它允许签名不使用泛型类型的方法覆盖该方法的任何泛化版本。
而你正在采用另一种方式:一种泛型类型的方法覆盖一种没有泛型的方法。
由于删除的签名相同,因此 4也成立:public Object get(int i)
答案 3 :(得分:1)
想象一下以下的调用。
A a = new C();
a.get(0);
实际上,您正在调用泛型方法,但是您没有传入任何类型的参数。事实上,这不是一个问题。无论如何,这些类型参数在代码生成期间消失。然而,从未取消过具体化,并且语言已经尝试过Java的管理员continue to try并保持开放。如果定义了类型参数,则您的调用不会向需要一个的方法提供任何参数。
答案 4 :(得分:1)
@ RafaelT的原始代码导致此编译错误...
C.java:3: error: C is not abstract and does not override abstract method get(int) in A
public class C extends A {
^
C.java:5: error: name clash: <T>get(int) in C and get(int) in A have the same erasure, yet neither overrides the other
public <T extends Object> T get ( int i ){
^
where T is a type-variable:
T extends Object declared in method <T>get(int)
让@ RafaelT的C子类成功编译的最简单,最直接的解决方案是......
public abstract class A {
public abstract Object get(int i);
}
public class C<T> extends A {
@Override
public T get(int i){
//impl
return (T)someObj;
}
}
虽然我确信其他人对他们的答案意味着很好。然而,一些答案似乎已经严重误解the JLS。
以上只对类声明的更改导致编译成功。仅这一事实意味着@RafaelT的原始签名确实完全正常as subsignatures - 与其他人的建议相反。
我不会犯同样的错误,其他人回答似乎已经做了,并试图假装我完全了解JLS泛型文档。我承认,我还没有100%确定,这是OP原始编译失败的根本原因。
但是我怀疑它有 与OP使用Object
作为the return type的不幸组合。 Java杂志中典型的令人困惑和古怪的微妙之处。
答案 5 :(得分:0)
您应该阅读erasure。
在你的例子中:
public class D extends A{
@Override
public Object get(int i){
//impl
}
public <T extends Object> T get(int i){
//impl
}
}
编译器生成的泛型方法的字节代码与非泛型方法相同;也就是说,T
将被替换为上限,即Object
。这就是您在IDE中收到DuplicateMethod
警告的原因。
答案 6 :(得分:0)
如果没有在其他地方声明<T extends Object> T get(int i)
- 在方法的参数或封闭类的字段中,将方法声明为T
是没有意义的。在后一种情况下,只需使用类型T
参数化整个类。
答案 7 :(得分:0)
存在一个概念性问题。假设C#get()
会覆盖A#get()
A a = new C();
Object obj = a.get(); // actually calling C#get()
但C#get()
需要T
- T
应该是什么?没有办法确定。
您可以抗议由于删除而不需要T
。今天这是正确的。但是擦除被认为是一种“临时”的解决方法。类型系统在很大程度上不假设擦除;它实际上是经过精心设计的,因此可以在未来版本的Java中完全“可恢复”,即不需要擦除,而不会破坏现有代码。