class Test{
void add(Set<Integer> ii){}
void add(Set<String> ss){}
}
我得到了
compilation error
方法add(Set)与Test类型中的另一个方法具有相同的擦除add(Set)。
虽然我可以解决它,但我想知道为什么javac不喜欢这样。 br />
我可以看到,在很多情况下,这两种方法的逻辑非常相似,可以用一个
public void add(Set<?> set){}
方法代替,但情况并非总是如此。
如果要有两个带这些参数的
constructors
,这会很烦人,因为那样就不能只更改其中一个constructors
的名称。#1 楼
该规则旨在避免仍然使用原始类型的旧代码中的冲突。这里是从JLS提取为什么不允许这样做的说明。假设在将泛型引入Java之前,我编写了一些类似这样的代码:
class CollectionConverter {
List toList(Collection c) {...}
}
您扩展我的类,就像这样:
class Overrider extends CollectionConverter{
List toList(Collection c) {...}
}
引入泛型之后,我决定更新我的库。
class CollectionConverter {
<T> List<T> toList(Collection<T> c) {...}
}
您还没有准备好进行任何更新,因此您离开您的
Overrider
课程。为了正确地覆盖toList()
方法,语言设计人员认为原始类型对于任何泛型类型都是“覆盖等效”的。这意味着,尽管您的方法签名在形式上不再与我的超类的签名相同,但是您的方法仍将被覆盖。现在,时间流逝,您决定准备好更新类了。但是您花了些力气,而不是编辑现有的原始
toList()
方法,而是添加了一个新方法,如下所示:class Overrider extends CollectionConverter {
@Override
List toList(Collection c) {...}
@Override
<T> List<T> toList(Collection<T> c) {...}
}
由于raw的覆盖等效性类型,则这两种方法的格式均有效,可以覆盖
toList(Collection<T>)
方法。但是,当然,编译器需要解析一个方法。为了消除这种歧义,类不允许具有多个等效的方法-即,擦除后具有相同参数类型的多个方法。关键是这是设计的语言规则保持使用原始类型与旧代码的兼容性。这不是擦除类型参数所要求的限制;因为方法解析是在编译时发生的,所以将通用类型添加到方法标识符中就足够了。
评论
很好的答案和榜样!但是,我不确定我是否完全理解您的最后一句话(“由于方法解析是在编译时发生的,因此在擦除之前,不需要类型化即可完成此工作。”)。您能详细说明一下吗?
–乔纳斯·艾希尔(Jonas Eicher)
2012年12月4日13:56
说得通。我只是花了一些时间考虑模板方法中的类型化,但是,是的:编译器确保在擦除类型之前选择正确的方法。美丽。如果没有被遗留代码兼容性问题所污染。
–乔纳斯·艾希尔(Jonas Eicher)
2012年12月5日在9:03
自上次评论或在此处编辑以来已经过去了几年。也许Java 8有什么改变吗?告诉我,javac有一个命令行选项,它允许它放弃旧的类。那会让我开心!
–daveloyall
2014年12月4日,0:23
@daveloyall不,我不知道javac的这种选项。
– erickson
2014年12月4日,0:32
这不是我第一次遇到Java错误,这根本不是错误,如果只有Java的作者像其他人一样使用警告,则可以将其编译。只有他们认为自己更了解一切。
–TomášZato-恢复莫妮卡
15年4月12日在22:50
#2 楼
Java泛型使用类型擦除。尖括号(<Integer>
和<String>
)中的位被删除,因此您最终会遇到两种具有相同签名的方法(您在错误中看到的add(Set)
)。这是不允许的,因为运行时不知道每种情况下使用哪种。如果Java获得了泛型化泛型,则可以执行此操作,但是现在不太可能了。
评论
很抱歉,您的答案(以及其他答案)没有解释为什么这里有错误。重载解析是在编译时完成的,并且编译器肯定具有类型信息,该类型信息决定了通过地址或字节码中引用的任何方法链接哪个方法,我认为这不是签名。我什至认为某些编译器将允许对此进行编译。
– Stilgar
2010-6-25 at 12:32
@Stilgar是什么阻止通过反射调用或检查方法?由Class.getMethods()返回的方法列表将具有两个相同的方法,这毫无意义。
– Adrian Mouat
11 Mar 4 '11 at 10:42
反射信息可以/应该包含使用泛型所需的元数据。如果不是,那么在导入已编译的库时,Java编译器如何了解通用方法?
– Stilgar
2011年3月4日在12:22
然后需要修复getMethod方法。例如,引入一个重载,该重载指定了泛型重载,并使原始方法仅返回非泛型版本,而不返回任何标注为泛型的方法。当然,这应该在1.5版中完成。如果他们现在这样做,将破坏该方法的向后兼容性。我支持我的声明,即类型擦除并不决定这种行为。可能由于资源有限,导致实现工作量不足。
– Stilgar
2011-3-5 14:56
这不是一个精确的答案,但是确实可以用一种有用的虚构来快速地总结出这个问题:方法签名太相似了,编译器可能没有说出区别,然后您会遇到“未解决的编译问题”。
–世界
13年10月8日在16:45
#3 楼
这是因为Java泛型是使用Erasure类型实现的。您的方法将在编译时转换为类似的内容:
方法解析在编译时发生,而不会•考虑类型参数。 (请参阅erickson的答案)。
void add(Set ii);
void add(Set ss);
这两种方法都具有相同的签名,但没有类型参数,因此会出现错误。
#4 楼
问题是Set<Integer>
和Set<String>
实际上被JVM视为Set
。选择Set的类型(在您的情况下为String或Integer)仅是编译器使用的语法糖。 JVM无法区分Set<String>
和Set<Integer>
。评论
确实,JVM运行时没有用于区分每个Set的信息,但是由于方法解析是在编译时发生的,因此当必要的信息可用时,这无关紧要。问题在于,允许这些重载会与原始类型的允许冲突,因此在Java语法中将它们视为非法。
– erickson
2011-12-11 21:21
@erickson即使编译器知道要调用的方法,也无法像字节码中那样调用它们,它们看上去完全一样。您需要更改方法调用的指定方式,如(Ljava / util / Collection;)Ljava / util / List;。不起作用。您可以使用(Ljava / util / Collection
– maaartinus
18/09/18在15:26
@maaartinus是的,我同意您必须更改方法说明符。我正在尝试解决一些导致他们放弃尝试的无法解决的问题。
– erickson
18-09-18在16:44
#5 楼
定义一个没有类型的单个方法,例如void add(Set ii){}
您可以在根据选择调用方法时提及类型。它适用于任何类型的集合。
#6 楼
编译器可能会用Java字节码将Set(Integer)转换为Set(Object)。如果是这种情况,则Set(Integer)仅在编译阶段用于语法检查。评论
从技术上讲,它只是原始类型Set。泛型不存在于字节码中,它们是强制转换的语法糖,并提供了编译时类型的安全性。
–安德烈·道尔(Andrzej Doyle)
2010年1月4日13:40
#7 楼
尝试编写类似以下内容时遇到了这个问题:Continuable<T> callAsync(Callable<T> code) {....}
和
Continuable<Continuable<T>> callAsync(Callable<Continuable<T>> veryAsyncCode) {...}
它们对于编译器来说是
Continuable<> callAsync(Callable<> veryAsyncCode) {...}
的两个定义>从字面上看,类型擦除意味着从泛型中擦除类型参数信息。
这很烦人,但这是Java在一段时间内的局限性。
对于构造函数来说,不能做太多的事情,2例如,新的专门在构造函数中使用不同参数的子类。
或改用具有不同名称的初始化方法...(虚拟构造函数?)...
对于类似的操作方法重命名会有所帮助,像
class Test{
void addIntegers(Set<Integer> ii){}
void addStrings(Set<String> ss){}
}
或者带有一些更具描述性的名称,可以自动记录oyu案件,例如
addNames
和addIndexes
等。
评论
如果您用完了数据结构又需要更多版本怎么办?您可以创建从基本版本继承的自定义类。
OP,您是否提出了一些构造函数问题的解决方案?我需要接受两种List,但我不知道如何处理。
使用Java时,我真的很想念C#...
@TomášZato,我通过向构造函数添加伪参数来解决此问题:布尔型noopSignatureOverload。