简体   繁体   English

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

[英]Method has the same erasure as another method in type

Why is it not legal to have the following two methods in the same class?为什么在同一个class中有下面两种方法是不合法的?

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

I get the compilation error我收到compilation error

Method add(Set) has the same erasure add(Set) as another method in type Test.方法 add(Set) 与类型 Test 中的另一个方法具有相同的擦除 add(Set)。

while I can work around it, I was wondering why javac doesn't like this.虽然我可以解决它,但我想知道为什么 javac 不喜欢这个。

I can see that in many cases, the logic of those two methods would be very similar and could be replaced by a single我可以看到在很多情况下,这两种方法的逻辑非常相似,可以用一个单独的方法代替

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

method, but this is not always the case.方法,但情况并非总是如此。

This is extra annoying if you want to have two constructors that takes those arguments because then you can't just change the name of one of the constructors .如果您希望有两个采用这些 arguments 的constructors ,这会非常烦人,因为这样您就不能只更改其中一个constructors的名称。

This rule is intended to avoid conflicts in legacy code that still uses raw types.此规则旨在避免仍然使用原始类型的遗留代码中的冲突。

Here's an illustration of why this was not allowed, drawn from the JLS.这是为什么不允许这样做的说明, 来自 JLS。 Suppose, before generics were introduced to Java, I wrote some code like this:假设,在泛型被引入 Java 之前,我写了一些这样的代码:

class CollectionConverter {
  List toList(Collection c) {...}
}

You extend my class, like this:你扩展我的类,像这样:

class Overrider extends CollectionConverter{
  List toList(Collection c) {...}
}

After the introduction of generics, I decided to update my library.在引入泛型之后,我决定更新我的库。

class CollectionConverter {
  <T> List<T> toList(Collection<T> c) {...}
}

You aren't ready to make any updates, so you leave your Overrider class alone.您还没有准备好进行任何更新,因此您不要Overrider您的Overrider类。 In order to correctly override the toList() method, the language designers decided that a raw type was "override-equivalent" to any generified type.为了正确覆盖toList()方法,语言设计者决定原始类型与任何泛化类型“覆盖等效”。 This means that although your method signature is no longer formally equal to my superclass' signature, your method still overrides.这意味着尽管您的方法签名不再正式等于我的超类的签名,但您的方法仍然会覆盖。

Now, time passes and you decide you are ready to update your class.现在,随着时间的流逝,您决定已准备好更新您的课程。 But you screw up a little, and instead of editing the existing, raw toList() method, you add a new method like this:但是你搞砸了一点,而不是编辑现有的原始toList()方法,你添加了一个像这样的新方法:

class Overrider extends CollectionConverter {
  @Override
  List toList(Collection c) {...}
  @Override
  <T> List<T> toList(Collection<T> c) {...}
}

Because of the override equivalence of raw types, both methods are in a valid form to override the toList(Collection<T>) method.由于原始类型的覆盖等效性,这两种方法都是覆盖toList(Collection<T>)方法的有效形​​式。 But of course, the compiler needs to resolve a single method.但是当然,编译器需要解析单个方法。 To eliminate this ambiguity, classes are not allowed to have multiple methods that are override-equivalent—that is, multiple methods with the same parameter types after erasure.为了消除这种歧义,类不允许有多个覆盖等效的方法——即擦除后具有相同参数类型的多个方法。

The key is that this is a language rule designed to maintain compatibility with old code using raw types.关键是这是一个语言规则,旨在保持与使用原始类型的旧代码的兼容性。 It is not a limitation required by the erasure of type parameters;不是擦除类型参数所要求的限制; because method resolution occurs at compile-time, adding generic types to the method identifier would have been sufficient.因为方法解析发生在编译时,所以向方法标识符添加泛型类型就足够了。

Java generics uses type erasure. Java 泛型使用类型擦除。 The bit in the angle brackets ( <Integer> and <String> ) gets removed, so you'd end up with two methods that have an identical signature (the add(Set) you see in the error).尖括号( <Integer><String> )中的位被删除,因此您最终会得到两个具有相同签名的方法(您在错误中看到的add(Set) )。 That's not allowed because the runtime wouldn't know which to use for each case.这是不允许的,因为运行时不知道对每种情况使用哪个。

If Java ever gets reified generics, then you could do this, but that's probably unlikely now.如果 Java 曾经得到具体化的泛型,那么您可以这样做,但现在可能不太可能。

This is because Java Generics are implemented with Type Erasure .这是因为 Java 泛型是使用Type Erasure实现的。

Your methods would be translated, at compile time, to something like:你的方法会在编译时被翻译成类似的东西:

Method resolution occurs at compile time and doesn't consider type parameters.方法解析发生在编译时,不考虑类型参数。 ( see erickson's answer ) 见埃里克森的回答

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

Both methods have the same signature without the type parameters, hence the error.两种方法都具有相同的签名,但没有类型参数,因此会出现错误。

The problem is that Set<Integer> and Set<String> are actually treated as a Set from the JVM.问题是Set<Integer>Set<String>实际上被视为来自 JVM 的Set Selecting a type for the Set (String or Integer in your case) is only syntactic sugar used by the compiler.为 Set 选择类型(在您的情况下是 String 或 Integer)只是编译器使用的语法糖。 The JVM can't distinguish between Set<String> and Set<Integer> . JVM 无法区分Set<String>Set<Integer>

Define a single Method without type like void add(Set ii){}定义一个没有类型的方法,如void add(Set ii){}

You can mention the type while calling the method based on your choice.您可以根据自己的选择在调用方法时提及类型。 It will work for any type of set.它适用于任何类型的集合。

It could be possible that the compiler translates Set(Integer) to Set(Object) in java byte code.编译器有可能将 Java 字节码中的 Set(Integer) 转换为 Set(Object)。 If this is the case, Set(Integer) would be used only at compile phase for syntax checking.如果是这种情况, Set(Integer) 将仅在编译阶段用于语法检查。

I bumped into this when tried to write something like: Continuable<T> callAsync(Callable<T> code) {....} and Continuable<Continuable<T>> callAsync(Callable<Continuable<T>> veryAsyncCode) {...} They become for compiler the 2 definitions of Continuable<> callAsync(Callable<> veryAsyncCode) {...}我在尝试编写以下内容时遇到了这个问题: Continuable<T> callAsync(Callable<T> code) {....}Continuable<Continuable<T>> callAsync(Callable<Continuable<T>> veryAsyncCode) {...}它们成为编译器的 2 个定义Continuable<> callAsync(Callable<> veryAsyncCode) {...}

The type erasure literally means erasing of type arguments information from generics.类型擦除字面意思是从泛型中擦除类型参数信息。 This is VERY annoying, but this is a limitation that will be with Java for while.这很烦人,但这是 Java 暂时存在的限制。 For constructors case not much can be done, 2 new subclasses specialized with different parameters in constructor for example.对于构造函数的情况,无能为力,例如在构造函数中专门使用不同参数的 2 个新子类。 Or use initialization methods instead... (virtual constructors?) with different names...或者改用初始化方法......(虚拟构造函数?)具有不同的名称......

for similar operation methods renaming would help, like对于类似的操作方法,重命名会有所帮助,例如

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

Or with some more descriptive names, self-documenting for oyu cases, like addNames and addIndexes or such.或者使用一些更具描述性的名称,对addNames案例进行自我记录,例如addNamesaddIndexes等。

In this case can use this structure:在这种情况下可以使用这个结构:

class Test{
   void add(Integer ... ii){}
   void add(String ... ss){}
}

and inside methods can create target collections内部方法可以创建目标 collections

void add(Integer ... values){
   this.values = Arrays.asList(values);
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM