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


381

为什么在同一类中使用以下两种方法是不合法的?

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


1
如果您用完了数据结构并且仍然需要更多版本该怎么办?
Omry Yadan 2014年

1
您可以创建自基本版本继承的自定义类。
willlma 2014年

1
OP,您是否提出了一些构造函数问题的解决方案?我需要接受两种List,但我不知道如何处理。
托马什Zato -恢复莫妮卡

9
使用Java时,我真的很想念C#...
Svish

1
@TomášZato,我通过向构造函数添加伪参数来解决此问题:布尔型noopSignatureOverload。
Nthalk

Answers:


357

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

这是从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>)方法。但是,当然,编译器需要解析一个方法。为了消除这种歧义,不允许类具有多个等效的方法-即,擦除后具有相同参数类型的多个方法。

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


3
很好的答案和榜样!但是,我不确定我是否完全理解您的最后一句话(“由于方法解析是在编译时发生的,因此在擦除之前,不需要类型转换来完成此工作。”)。您能详细说明一下吗?
乔纳斯·艾希

2
说得通。我只是花了一些时间考虑模板方法中的类型验证,但是,是的:编译器确保在擦除类型之前选择正确的方法。美丽。如果不被遗留代码兼容性问题所污染。
Jonas Eicher

1
@daveloyall不,我不知道有这样的选择javac
erickson

13
这不是我第一次遇到Java错误,这根本不是错误,如果只有Java的作者像其他人一样使用警告,则可以将其编译。只有他们认为自己更了解一切。
托马什Zato -恢复莫妮卡

1
@TomášZato不,我不知道有任何清洁的解决方法。如果您想要脏东西,可以传递List<?>enum定义的类型来指示类型。特定于类型的逻辑实际上可以在枚举上的方法中。或者,您可能想要创建两个不同的类,而不是创建一个具有两个不同构造函数的类。通用逻辑将在两种类型都委托给的超类或辅助对象中。
erickson

117

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

如果Java曾经获得过泛化的泛型,那么您可以这样做,但是现在不太可能。


24
很抱歉,您的答案(以及其他答案)没有解释为什么这里有错误。重载解析是在编译时完成的,编译器肯定具有类型信息,该类型信息决定了通过地址或字节码中引用的任何方法链接哪个方法,我认为这不是签名。我什至认为某些编译器将允许对此进行编译。
Stilgar 2010年

5
@Stilgar是什么阻止通过反射调用或检查方法?由Class.getMethods()返回的方法列表将具有两个相同的方法,这毫无意义。
Adrian Mouat

5
反射信息可以/应该包含使用泛型所需的元数据。如果不是,那么在导入已编译的库时,Java编译器如何知道通用方法?
Stilgar 2011年

4
然后需要修复getMethod方法。例如,引入一个重载,该重载指定了泛型重载,并使原始方法仅返回非泛型版本,而不返回任何标注为泛型的方法。当然,这应该在1.5版中完成。如果他们现在这样做,将破坏该方法的向后兼容性。我支持我的声明,即类型擦除并不决定这种行为。可能是由于资源有限,导致实现工作量不足。
斯蒂尔加2011年

1
这不是一个精确的答案,但是确实可以用一种有用的虚构来快速地总结出这个问题:方法签名太相似了,编译器可能没有说出区别,然后您会遇到“未解决的编译问题”。
worc

46

这是因为Java泛型是使用Type Erasure实现的。

您的方法将在编译时转换为以下内容:

方法解析发生在编译时,并且不考虑类型参数。(请参阅埃里克森的答案

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

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


21

问题是,Set<Integer>Set<String>实际的处理Set从JVM。选择Set的类型(在您的情况下为String或Integer)仅是编译器使用的语法糖。JVM无法区分Set<String>Set<Integer>


7
的确,JVM 运行时没有信息来区分每个Set,但是由于方法解析是在编译时发生的,因此当必要的信息可用时,这无关紧要。问题在于,允许这些重载会与原始类型的允许冲突,因此在Java语法中将它们视为非法。
erickson

@erickson即使编译器知道要调用的方法,也不能像字节码中那样调用它们,它们看上去完全一样。您需要更改方法调用的指定方式,这(Ljava/util/Collection;)Ljava/util/List;是行不通的。您可以使用 (Ljava/util/Collection<String>;)Ljava/util/List<String>;,但这是一个不兼容的更改,在您所拥有的只是擦除类型的地方,您会遇到无法解决的问题。您可能必须完全删除擦除,但这非常复杂。
maaartinus

@maaartinus是的,我同意您必须更改方法说明符。我试图解决导致他们放弃该尝试的一些无法解决的问题。
埃里克森

7

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

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


3

编译器可能会用Java字节码将Set(Integer)转换为Set(Object)。在这种情况下,Set(Integer)仅在编译阶段用于语法检查。


5
从技术上讲,它只是原始类型Set。泛型不存在于字节码中,它们是强制转换的语法糖,并提供了编译时类型的安全性。
Andrzej Doyle'1

1

我碰上这个时候试着写类似: Continuable<T> callAsync(Callable<T> code) {....}Continuable<Continuable<T>> callAsync(Callable<Continuable<T>> veryAsyncCode) {...} 他们成为了编译器的定义2 Continuable<> callAsync(Callable<> veryAsyncCode) {...}

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

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

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

或者用一些描述性的名称,自我记录的奥尤情况下,如addNamesaddIndexes或这样的。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.