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

0tdrvxhp  于 2023-01-29  发布在  Java
关注(0)|答案(8)|浏览(180)

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

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

rslzwgfq

rslzwgfq1#

此规则旨在避免在仍使用原始类型的旧代码中发生冲突。
下面的例子解释了为什么这是不允许的,它来自于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>)方法的有效形式。但是当然,编译器需要解析单个方法。为了消除这种歧义,类不允许有多个重写等价的方法--也就是说,在擦除后,多个方法具有相同的参数类型。
关键是这是一个语言规则,设计用来维护与使用原始类型的旧代码的兼容性,而不是擦除类型参数所要求的限制;因为方法解析发生在编译时,所以向方法标识符添加泛型类型就足够了。

j8ag8udp

j8ag8udp2#

Java泛型使用类型擦除。尖括号中的位(<Integer><String>)被删除,所以你最终会得到两个具有相同签名的方法(你在错误中看到的add(Set))。这是不允许的,因为运行时不知道每种情况下使用哪一个。
如果Java有了具体化的泛型,那么你就可以这样做,但现在可能不太可能了。

tcomlyy6

tcomlyy63#

这是因为Java泛型是用Type Erasure实现的。
您的方法将在编译时被转换为如下形式:
方法解析发生在编译时,不考虑类型参数。(see erickson's answer

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

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

eqzww0vc

eqzww0vc4#

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

y53ybaqx

y53ybaqx5#

定义一个没有void add(Set ii){}类型的方法
你可以根据自己的选择在调用方法时提及类型。它对任何类型的集合都有效。

5lwkijsr

5lwkijsr6#

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

xxe27gdn

xxe27gdn7#

当我试着写这样的东西时,我碰到了这个:Continuable<T> callAsync(Callable<T> code) {....}Continuable<Continuable<T>> callAsync(Callable<Continuable<T>> veryAsyncCode) {...}对于编译器,它们成为Continuable<> callAsync(Callable<> veryAsyncCode) {...}的2个定义
类型擦除字面上的意思是从泛型中擦除类型参数信息。这是非常烦人的,但这是Java的一个限制。对于构造函数的情况,没有太多可以做的,例如,2个新的子类在构造函数中使用不同的参数。或者使用初始化方法代替...(虚拟构造函数?)使用不同的名称...
对于类似的操作方法,重命名会有所帮助,例如

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

或者使用一些更具描述性的名称,为oyu案例自文档化,如addNamesaddIndexes等。

von4xj4u

von4xj4u8#

在这种情况下可以使用以下结构:

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

内部方法可以创建目标集合

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

相关问题