方法与另一种类型的方法具有相同的擦除
为什么在同一个班上有这两种方法是不合法的?
class Test{ void add(Set<Integer> ii){} void add(Set<String> ss){} }
我收到compilation error
方法add(Set)与Test类型中的另一个方法具有相同的擦除add(Set)。
而我可以解决它,我想知道为什么javac不喜欢这个。
我可以看到,在很多情况下,这两种方法的逻辑非常相似,可以用一个单一的方法来代替
public void add(Set<?> set){}
方法,但这并不总是如此。
如果你想要有两个constructors
来接受这些参数,那么这是非常烦人的,因为那样你就不能只改变一个constructors
的名字。
此规则旨在避免使用原始类型的遗留代码中的冲突。
下面是为什么这是不允许的, 从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>)
方法。 但是,当然,编译器需要解决一个单一的方法。 为了消除这种不明确性,类不允许有多个与override等效的方法 – 也就是说,擦除之后具有相同参数类型的多个方法。
关键是这是一种语言规则,旨在保持与使用原始类型的旧代码的兼容性。 它不是类型参数擦除所要求的限制; 因为方法解析发生在编译时,添加泛型类型到方法标识符就足够了。
Java泛型使用类型擦除。 尖括号( <Integer>
和<String>
)中的位被移除,所以你最终会得到两个具有相同签名的方法(你在错误中看到的add(Set)
)。 这是不允许的,因为运行时将不知道哪个用于每个案件。
如果Java获得了泛化的泛型,那么你可以做到这一点,但现在可能不太可能。
这是因为Java泛型是通过Type Erasure实现的。
你的方法在编译时会被翻译成如下形式:
方法解析发生在编译时,不考虑类型参数。 ( 见埃里克森的答案 )
void add(Set ii); void add(Set ss);
两种方法都具有相同的签名而没有类型参数,因此是错误的。
问题是Set<Integer>
和Set<String>
实际上被当作JVM的Set
。 为Set(字符串或Integer类型)选择一个类型只是编译器使用的语法糖。 JVM无法区分Set<String>
和Set<Integer>
。
编译器可能会用java字节码翻译Set(Integer)为Set(Object)。 如果是这种情况,Set(Integer)将仅在编译阶段用于语法检查。
定义一个没有类型的方法,如void add(Set ii){}
您可以在根据您的选择调用方法时提及该类型。 它将适用于任何类型的集合。