泛型类在 Java 6 中编译,但不在 Java 7 中编译

2022-09-03 01:52:33

我在Java 6中有一个正确编译的接口:

public interface IMultiMap<K, V> extends Map<K, Set<V>> {

    public int valueSize();

    public boolean put(K key, V value);

    public void clear(Object key);

    public boolean isEmpty(Object key);
}

但是在Java 7中,这个接口无法编译。我得到一个编译错误,它与具有相同的擦除。来自编译器的完整错误:boolean put(K, V)V put(K, V)

error: name clash: put(K#1,V#1) in IMultiMap and put(K#2,V#2) in Map have the same erasure, yet neither overrides the other
    public boolean put(K key, V value);
  where K#1,V#1,K#2,V#2 are type-variables:
    K#1 extends Object declared in interface IMultiMap
    V#1 extends Object declared in interface IMultiMap
    K#2 extends Object declared in interface Map
    V#2 extends Object declared in interface Map

对于记录,添加任何类型的覆盖都不起作用。我尝试显式覆盖 ,但错误仍然存在。更改 my 的返回类型是没有意义的,因为此错误会阻止达到该潜在错误,并且如果修复此错误,则这两种方法无论如何都不会具有相同的名称/参数签名。Map.putput

我想我可能会尝试对Java 6进行一些反思,看看Java 6编译字节码中的实际参数类型最终是什么。很明显,这两种 Java 7 方法都被擦除到 。一旦我这样做,我会在这里发布反射结果。put(Object, Object)

同时,我的临时解决方法是重命名为 ,但这个新行为是否正确?Java 7 的泛型规范的某些部分是否发生了变化,使得旧的 Java 6 行为出错了?或者这是Java 7编译器中的一个错误?putputSingle

提前致谢。

编辑:我运行了反射代码。在下面查看我的答案。


答案 1

我认为这是1.6中的一个错误,在1.7中得到了修复。本页摘录:

概要:一个类不能定义两个具有相同已擦除签名但具有两个不同返回类型
的方法 说明:无论返回类型是否相同,一个类都不能定义具有相同擦除签名的两个方法。这遵循 JLS,Java SE 7 版,第 8.4.8.3 节。JDK 6编译器允许具有相同擦除签名但返回类型不同的方法;此行为不正确,已在 JDK 7 中修复。
例:

class A {
   int m(List<String> ls) { return 0; }
   long m(List<Integer> ls) { return 1; }
}

此代码在 JDK 5.0 和 JDK 6 下编译,在 JDK 7 下被拒绝。


答案 2

我在Java 6上运行了反射代码。

代码如下:

public static void main(String[] args) {
    Class<IMultiMap> immClass = IMultiMap.class;
    Method[] methods = immClass.getMethods();
    for (Method method : methods) {
        if (method.getName().equals("put"))
            System.out.println(method.toString());
    }
}

以下是该类的方法签名:

public abstract boolean IMultiMap.put(java.lang.Object,java.lang.Object)
public abstract java.lang.Object java.util.Map.put(java.lang.Object,java.lang.Object)

或者更简洁地说:

boolean put(Object, Object)
Object  put(Object, Object)

因此,它们被擦除为具有不同返回类型的相同参数。我猜这是Java 6 JLS中未指定边缘情况的一个错误,然后根据assylias的答案。我想知道Java 6是如何在运行时正确解析这些方法的?

编辑:根据x4u的评论,调用字节码在编译时保持对整个签名的引用,因此这就是JVM调用正确方法的原因。由于编译器可能能够分辨出我调用了哪个方法,因为它可以访问源代码(从而访问泛型信息),因此编译器可能通过整个签名将其链接到正确的方法。有趣的知识!