为什么在同一个类中有以下两个方法是不法律的的?
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
的名称。
8条答案
按热度按时间rslzwgfq1#
此规则旨在避免在仍使用原始类型的旧代码中发生冲突。
下面的例子解释了为什么这是不允许的,它来自于JLS。假设在泛型被引入Java之前,我编写了如下代码:
你扩展了我的类,像这样:
泛型引入后,我决定更新我的库。
你还没有准备好做任何更新,所以你不去管你的
Overrider
类。为了正确地重写toList()
方法,语言设计者决定一个原始类型与任何泛化类型“重写等价”。这意味着尽管你的方法签名不再形式上等于我的超类的签名,你的方法仍然重写。现在,随着时间的推移,您决定更新类,但是您犯了一个小错误,您没有编辑现有的原始
toList()
方法,而是 * 添加 * 了一个新方法,如下所示:因为原始类型的重写等价性,两个方法都是重写
toList(Collection<T>)
方法的有效形式。但是当然,编译器需要解析单个方法。为了消除这种歧义,类不允许有多个重写等价的方法--也就是说,在擦除后,多个方法具有相同的参数类型。关键是这是一个语言规则,设计用来维护与使用原始类型的旧代码的兼容性,而不是擦除类型参数所要求的限制;因为方法解析发生在编译时,所以向方法标识符添加泛型类型就足够了。
j8ag8udp2#
Java泛型使用类型擦除。尖括号中的位(
<Integer>
和<String>
)被删除,所以你最终会得到两个具有相同签名的方法(你在错误中看到的add(Set)
)。这是不允许的,因为运行时不知道每种情况下使用哪一个。如果Java有了具体化的泛型,那么你就可以这样做,但现在可能不太可能了。
tcomlyy63#
这是因为Java泛型是用Type Erasure实现的。
您的方法将在编译时被转换为如下形式:
方法解析发生在编译时,不考虑类型参数。(see erickson's answer)
两个方法具有相同的签名,但没有类型参数,因此出现错误。
eqzww0vc4#
问题是
Set<Integer>
和Set<String>
实际上被JVM视为Set
。为Set选择类型(在本例中为String或Integer)只是编译器使用的语法糖。JVM无法区分Set<String>
和Set<Integer>
。y53ybaqx5#
定义一个没有
void add(Set ii){}
类型的方法你可以根据自己的选择在调用方法时提及类型。它对任何类型的集合都有效。
5lwkijsr6#
编译器可能会将Java字节码中的Set(Integer)转换为Set(Object)。如果是这种情况,Set(Integer)将仅在编译阶段用于语法检查。
xxe27gdn7#
当我试着写这样的东西时,我碰到了这个:
Continuable<T> callAsync(Callable<T> code) {....}
和Continuable<Continuable<T>> callAsync(Callable<Continuable<T>> veryAsyncCode) {...}
对于编译器,它们成为Continuable<> callAsync(Callable<> veryAsyncCode) {...}
的2个定义类型擦除字面上的意思是从泛型中擦除类型参数信息。这是非常烦人的,但这是Java的一个限制。对于构造函数的情况,没有太多可以做的,例如,2个新的子类在构造函数中使用不同的参数。或者使用初始化方法代替...(虚拟构造函数?)使用不同的名称...
对于类似的操作方法,重命名会有所帮助,例如
或者使用一些更具描述性的名称,为oyu案例自文档化,如
addNames
和addIndexes
等。von4xj4u8#
在这种情况下可以使用以下结构:
内部方法可以创建目标集合