Java 6中泛型类可以编译,但在Java 7中无法编译

17

我在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

顺便说一句,任何类型的覆盖都行不通。我试过显式重写Map.put,但错误仍然出现。改变我的put的返回类型是无意义的,因为这个错误阻止了潜在的错误被达到,如果这个错误被修复了,那么这两个方法也不会有相同的名称/参数签名。

我想我可能会尝试一些 Java 6 上的反射,并看看 Java 6 编译后的实际参数类型是什么。很明显,两个 Java 7 方法都被擦除为put(Object, Object)。我会在这里发布反射结果。

与此同时,我的临时解决方法将只是将put重命名为putSingle,但这种新行为是否正确?Java 7 的泛型规范的某些部分发生了变化,使得旧的 Java 6 行为不正确吗?还是这是 Java 7 编译器的一个 bug?

先谢谢了。

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


你使用的JDK版本是什么? 这对我来说可以正常工作。 - Jeffrey
算了,这个可以在Eclipse编译器上运行,但是在JDK 7u6上不行。 - Jeffrey
1.7.0_03。我会尝试更新。编辑:这是一个有趣的区别。 - Brian
无法在1.7.0_06上编译。您使用的是哪个版本的1.6? - assylias
我们的目标是针对6的JDK 6u17。 - Brian
2个回答

19

我认为这是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中被拒绝。


@Brian,其实这并不是javac的一个bug;语言规范没有考虑到“类型擦除”引入的一些边角情况。 - irreputable

1

我在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 调用了正确的方法。由于编译器可能能够通过其对源代码(因此对泛型信息)的访问来确定我正在调用哪个方法,因此编译器可能通过整个签名将其链接到正确的方法。很有趣!


1
在字节码级别,方法总是使用其完整签名引用,其中包括返回类型。因此,一个类可以有任意数量的具有相同名称和参数列表但不同返回类型的方法,您仍然可以调用它们中的任何一个。多年来,字节码混淆器一直依赖于此。编译器的工作始终是确保您无法直接创建这样的类。此外,如果您使用更具体的返回类型覆盖超级方法,则编译器需要创建桥接方法,因此它会创建两个仅在返回类型上不同的方法。 - x4u

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接