方法与类型中的另一种方法具有相同的擦除

时间:2010-01-04 09:58:50

标签: java generics

为什么将这两种方法放在同一个类中是不合法的?

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的名称。

7 个答案:

答案 0 :(得分:320)

此规则旨在避免仍使用原始类型的旧代码中的冲突。

以下是为什么不允许这样做的说明,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>)方法。但是,当然,编译器需要解决单个方法。为了消除这种歧义,不允许类有多个覆盖等效的方法 - 即擦除后具有相同参数类型的多个方法。

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

答案 1 :(得分:103)

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

如果Java得到了具体化的泛型,那么你可以做到这一点,但现在可能不太可能了。

答案 2 :(得分:40)

这是因为Java Generics是用Type Erasure实现的。

您的方法将在编译时翻译为:

方法解析在编译时发生,不考虑类型参数。 (see erickson's answer

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

两种方法都具有相同的签名而没有类型参数,因此错误。

答案 3 :(得分:19)

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

答案 4 :(得分:4)

定义一个没有void add(Set ii){}

类型的方法

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

答案 5 :(得分:3)

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

答案 6 :(得分:0)

尝试编写类似以下内容时遇到了这个问题: SimpleProductTallProduct 它们成为编译器的2个定义 *product

类型擦除从字面上是指从泛型中擦除类型参数信息。 这非常烦人,但这是Java会在一段时间内受到的限制。 对于构造函数而言,可以做的事情不多,例如2个专门在构造函数中使用不同参数的新子类。 或者使用初始化方法...(虚拟构造函数?)使用不同的名称...

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

Continuable<T> callAsync(Callable<T> code) {....}

或者使用一些更具描述性的名称,例如Continuable<Continuable<T>> callAsync(Callable<Continuable<T>> veryAsyncCode) {...}Continuable<> callAsync(Callable<> veryAsyncCode) {...}之类的oyu案例的自我记录。