在我的一个课程中,我想定义这两个方法:
private void add(List<ChangeSet> changeSetList) {
for (ChangeSet changeSet : changeSetList) {
add(changeSet);
}
}
private void add(List<Change> changeList) {
for (Change change : changeList) {
add(change);
}
}
然后我收到以下错误:
Method add(List<Change>) has the same erasure add(List<E>) as another method in type DataRetriever
为什么不允许这样做?这样的方法定义有什么问题?我应该怎么做才能避免它呢?我不想重命名其中一种方法。
答案 0 :(得分:11)
这就是Java的类型系统如何“工作”。泛型List<Change>
和List<ChangeSet>
实际上并不是不同的类型。泛型参数只是编译器执行某些检查和某些强制转换的提示。然而,就JVM和类型系统而言,两种类型实际上都被“擦除”到List<Object>
(或者只是List
,如果你愿意的话),这两种类型实际上是相同的,没有内部差异。因此,您不能在不同的泛型参数上实际重载,因为就重载决策而言,这两种类型是相同的。
答案 1 :(得分:7)
此限制是语言语法的一部分,而不是Java运行时本身。本质上,此规则旨在避免仍使用原始类型的遗留代码中的冲突。
像javac
这样的编译器将拒绝这种类型的重载,但是如果你通过其他方式创建一个类(编写自己的编译器,或使用像ASM这样的字节码工程库),签名只有不同的类型参数,javac
编译器将解析调用类中正确的方法。
以下是为什么不允许这样做的说明,drawn from the 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) {...}
}
由于原始类型的覆盖等价,两种方法都以有效的形式覆盖toList(Collection<T>)
方法。但是,当然,编译器需要解决单个方法。为了消除这种歧义,不允许类有多个覆盖等效的方法 - 即擦除后具有相同参数类型的多个方法。
关键是这是一种语言规则,旨在允许继续使用原始类型,而不是因类型参数的擦除而产生的限制。
如果您消除遗留代码(例如,使用您自己的,非严格Java语言),这种类型的重载功能就完美无缺。因为方法解析是在编译时发生的,所以在擦除之前,不需要输入类型来实现这一点。
答案 2 :(得分:2)
除了adarshr和Kerrek的答案之外,为什么不把它变成通用的,如下所示:
private <T> void add(List<T> changeList) {
for (T change : changeList) {
add(change);
}
}
这应该适用于两种情况......
答案 3 :(得分:1)
因为泛型只是编译时帮助你。编译后,字节码中不会存储与泛型相关的信息。
看看这个:
http://docs.oracle.com/javase/tutorial/java/generics/erasure.html
答案 4 :(得分:0)
在类型擦除之后,两种方法都将具有private void add(List)
的签名,这是不允许的。
您需要重命名方法或传递另一个参数,例如列表值的类。