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

Omr*_*dan 360 java generics

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

class Test{
   void add(Set<Integer> ii){}
   void add(Set<String> ss){}
}
Run Code Online (Sandbox Code Playgroud)

我明白了 compilation error

方法add(Set)具有与Test类型中的另一种方法相同的擦除add(Set).

虽然我可以解决它,但我想知道为什么javac不喜欢这个.

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

public void add(Set<?> set){}
Run Code Online (Sandbox Code Playgroud)

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

如果你想要有两个constructors接受这些参数的话,这是非常烦人的,因为那时你不能只改变其中一个的名字constructors.

eri*_*son 348

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

以下是从JLS中提取的不允许这样做的说明.假设,在将泛型引入Java之前,我写了一些这样的代码:

class CollectionConverter {
  List toList(Collection c) {...}
}
Run Code Online (Sandbox Code Playgroud)

你扩展我的课程,像这样:

class Overrider extends CollectionConverter{
  List toList(Collection c) {...}
}
Run Code Online (Sandbox Code Playgroud)

引入泛型后,我决定更新我的库.

class CollectionConverter {
  <T> List<T> toList(Collection<T> c) {...}
}
Run Code Online (Sandbox Code Playgroud)

您还没有准备好进行任何更新,因此您Overrider不必单独上课.为了正确覆盖该toList()方法,语言设计者决定原始类型与任何通用类型"覆盖等效".这意味着虽然您的方法签名不再正式等于我的超类'签名,但您的方法仍然会覆盖.

现在,时间过去了,您决定准备更新课程.但是你搞砸了一下,而不是编辑现有的raw toList()方法,你添加一个像这样的新方法:

class Overrider extends CollectionConverter {
  @Override
  List toList(Collection c) {...}
  @Override
  <T> List<T> toList(Collection<T> c) {...}
}
Run Code Online (Sandbox Code Playgroud)

由于原始类型的覆盖等价,两种方法都以有效的形式覆盖该toList(Collection<T>)方法.但是,当然,编译器需要解决单个方法.为了消除这种歧义,不允许类具有多个覆盖等效的方法 - 即擦除后具有相同参数类型的多个方法.

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

  • 这不是我第一次遇到没有错误的Java错误,只有Java的作者像其他人一样使用警告时才能编译.只有他们认为他们更了解一切. (11认同)
  • 很棒的答案和例子!但是,我不确定,如果我完全理解你的最后一句话("因为方法解析发生在编译时,在擦除之前,不需要输入类型来实现这项工作.").你能详细说一下吗? (3认同)
  • 说得通.我只是花了一些时间考虑模板方法中的类型实现,但是是的:编译器确保在类型擦除之前选择正确的方法.美丽.如果它没有被遗留代码兼容性问题所污染. (2认同)
  • @erickson当你的类有两个接受两个不同列表的构造函数时,例如“List&lt;String&gt;”和“List&lt;Integer&gt;”。它们对列表的操作不同,但由于此错误,它们不能单独存在。因此,我需要一种方法来在一个仅采用“List”的构造函数中定义这两个操作。我不知道如何检查正确的泛型类型并根据需要将给定的“List”转换为“List&lt;Something&gt;”。 (2认同)

Gar*_*ryF 113

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

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

  • 对不起,但你的回答(和其他答案)没有解释为什么这里有错误.重载解析是在编译时完成的,编译器肯定有所需的类型信息来决定通过地址链接哪个方法,或者通过字节码中引用的方法,我认为这不是签名.我甚至认为一些编译器会允许这个编译. (23认同)
  • @Stilgar是什么阻止通过反射调用或检查方法?Class.getMethods()返回的方法列表将有两个相同的方法,这是没有意义的. (5认同)
  • 反射信息可以/应该包含使用泛型所需的元数据.如果不是,当您导入已编译的库时,Java编译器如何知道通用方法? (5认同)
  • 然后getMethod方法需要修复.例如,引入一个指定泛型重载的重载,并使原始方法仅返回非泛型版本,而不返回任何通用的方法.当然这应该在1.5版本中完成.如果他们现在这样做,他们将破坏该方法的向后兼容性.我坚持认为类型擦除不会决定这种行为.由于资源有限,实施工作没有得到足够的工作. (4认同)

bru*_*nde 44

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

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

方法解析在编译时发生,不考虑类型参数.(见erickson的回答)

void add(Set ii);
void add(Set ss);
Run Code Online (Sandbox Code Playgroud)

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


kgi*_*kis 21

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

  • 确实,JVM*runtime*没有信息来区分每个`Set`,但由于方法解析在编译时发生,所以当必要的信息可用时,这是不相关的.问题是允许这些重载会与原始类型的允许冲突,因此它们在Java语法中被认为是非法的. (7认同)

Idr*_*raf 6

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

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