方法在类型上与另一个方法具有相同的擦除

为什么在同一个类中有以下两个方法是不合法的?

class Test{
void add(Set<Integer> ii){}
void add(Set<String> ss){}
}

我得到compilation error

方法add(Set)与类型Test中的另一个方法具有相同的擦除添加(Set)。

虽然我可以绕过它,但我想知道为什么javac不喜欢这样。

我可以看到,在许多情况下,这两个方法的逻辑非常相似,可以用一个方法来代替

public void add(Set<?> set){}

方法,但情况并非总是如此。

如果你想要有两个接受这些参数的constructors,这是非常烦人的,因为这样你就不能只改变其中一个constructors的名称。

214677 次浏览

Java泛型使用类型擦除。尖括号中的位(<Integer><String>)被移除,所以你最终会得到两个具有相同签名的方法(你在错误中看到的add(Set))。这是不允许的,因为运行时不知道在每种情况下使用哪个。

如果Java有了具体化的泛型,那么您就可以这样做,但现在可能不太可能。

有可能编译器在java字节代码中将Set(Integer)转换为Set(Object)。如果是这种情况,Set(Integer)将仅在编译阶段用于语法检查。

这是因为Java泛型是用类型擦除实现的。

在编译时,你的方法会被翻译成这样:

方法解析发生在编译时,不考虑类型参数。(请看埃里克森的回答)

void add(Set ii);
void add(Set ss);

这两个方法具有相同的签名,但没有类型参数,因此出现了错误。

问题是Set<Integer>Set<String>实际上被视为来自JVM的Set。为Set选择类型(在您的情况下是字符串或整数)只是编译器使用的语法糖。JVM无法区分Set<String>Set<Integer>

该规则旨在避免在仍然使用原始类型的遗留代码中发生冲突。

假设,在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>)方法的有效形式。当然,编译器需要解析单个方法。为了消除这种歧义,类不允许有多个重写等效的方法,也就是说,在擦除后,多个具有相同参数类型的方法。

关键在于,这是一种语言规则,旨在保持与使用原始类型的旧代码的兼容性。它不是删除类型参数所需要的限制;因为方法解析发生在编译时,向方法标识符添加泛型类型就足够了。

定义一个没有类型的方法,如void add(Set ii){}

您可以根据自己的选择在调用方法时提到类型。它适用于任何类型的集合。

当我试图写一些像这样的东西时,我碰到了这个: Continuable<T> callAsync(Callable<T> code) {....} 而且 Continuable<Continuable<T>> callAsync(Callable<Continuable<T>> veryAsyncCode) {...} 它们成为编译器的2个定义 Continuable<> callAsync(Callable<> veryAsyncCode) {...} < / p > 类型擦除字面意思是从泛型中擦除类型参数信息。 这是非常恼人的,但这是Java在一段时间内的一个限制。 对于构造函数的情况,可以做的不多,例如在构造函数中有2个新的子类专门使用不同的参数。 或者使用初始化方法…(虚拟构造函数?)使用不同的名称

对于类似的操作方法,重命名会有所帮助,比如

class Test{
void addIntegers(Set<Integer> ii){}
void addStrings(Set<String> ss){}
}

或者使用一些更描述性的名称,自记录oyu情况,如addNamesaddIndexes等。