【问题标题】:Ambiguous overloaded java methods with generics and varargs带有泛型和可变参数的模棱两可的重载 java 方法
【发布时间】:2012-05-17 19:25:35
【问题描述】:

我试图了解 java 如何处理函数调用中的歧义。在下面的代码中,对method 的调用是模棱两可的,但method2 不是!!!。

我觉得两者都模棱两可,但是为什么当我注释掉对method 的调用时会编译?为什么method2 也没有歧义?

public class A {
    public static <K> List<K> method(final K arg, final Object... otherArgs) {
        System.out.println("I'm in one");
        return new ArrayList<K>();
    }

    public static <K> List<K> method(final Object... otherArgs) {
        System.out.println("I'm in two");
        return new ArrayList<K>();
    }

    public static <K, V> Map<K, V> method2(final K k0, final V v0, final Object... keysAndValues) {
        System.out.println("I'm in one");
        return new HashMap<K,V> ();
    }

    public static <K, V> Map<K, V> method2(final Object... keysAndValues) {
        System.out.println("I'm in two");
        return new HashMap<K,V>();
    }

    public static void main(String[] args) {
        Map<String, Integer> c = A.method2( "ACD", new Integer(4), "DFAD" );
        //List<Integer> d = A.method(1, "2", 3  );
    }
}

编辑: 这出现在 cmets 中:到目前为止,许多 IDE 都报告为模棱两可 - IntelliJ 和 Netbeans。但是,它可以从命令行/maven 编译。

【问题讨论】:

  • 这真的会伤到你的头,但是,这是规范中的规则:docs.oracle.com/javase/specs/jls/se5.0/html/… - 我怀疑使用两个通用参数method2 可以说是“严格来说更多具体” - 我让你在那里做数学,我在吃午饭:-D
  • 对于javac 1.7.0_02,它无法编译,即使从命令行,method2 也被认为是模棱两可的。
  • 有趣的是,Java 7 中的编译行为有所不同。粗略一看,The spec 看起来很相似。
  • 感谢 @alain.janinm 我认为 JDK6 中存在错误。根据oracle.com/technetwork/java/javase/…,一些与模糊调用相关的错误已在 Java 7 中得到解决。我找不到这种特殊情况。
  • 可能,此外,根据 varargs doc you should not overload a varargs method, or it will be difficult for programmers to figure out which overloading gets called. 但是了解“最具体方法”的分辨率如何工作很有趣。如果您需要一些帮助来理解规范,您可以阅读 stackoverflow.com/q/6023439/1140748 以及一个很好解释的示例 forums.oracle.com/forums/message.jspa?messageID=9485871#9485871

标签: java generics variadic-functions


【解决方案1】:

测试method1是否比method2更具体的一种直观方法是查看method1是否可以通过使用相同参数调用method2来实现

method1(params1){
    method2(params1);   // if compiles, method1 is more specific than method2
}

如果有可变参数,我们可能需要扩展一个可变参数,以便两个方法具有相同数量的参数。

让我们检查一下示例中的前两个method()s

<K> void method_a(K arg, Object... otherArgs) {
    method_b(arg, otherArgs);   //ok L1
}
<K> void method_b(Object arg, Object... otherArgs) { // extract 1 arg from vararg
    method_a(arg, otherArgs);   //ok L2
}

(返回类型不用于确定特异性,因此省略)

两者都可以编译,因此每个都比另一个更具体,因此存在歧义。您的method2()s 也是如此,它们比彼此更具体。因此对method2() 的调用是模棱两可的,不应编译;否则是编译器错误。


这就是规范所说的;但这合适吗?当然,method_a 看起来比method_b 更具体。实际上如果我们有一个具体的类型而不是K

void method_a(Integer arg, Object... otherArgs) {
    method_b(arg, otherArgs);   // ok
}
void method_b(Object arg, Object... otherArgs) {
    method_a(arg, otherArgs);   // error
}

那么只有method_amethod_b 更具体,反之则不然。

差异源于类型推断的魔力。 L1/L2 调用没有显式类型参数的泛型方法,因此编译器尝试推断类型参数。类型推断算法的目标是找到类型参数以便代码编译!难怪 L1 和 L2 编译。 L2实际上推断为this.&lt;Object&gt;method_a(arg, otherArgs)

类型推断试图猜测程序员想要什么,但有时猜测肯定是错误的。我们真正的意图其实是

<K> void method_a(K arg, Object... otherArgs) {
    this.<K>method_b(arg, otherArgs);   // ok
}
<K> void method_b(Object arg, Object... otherArgs) {
    this.<K>method_a(arg, otherArgs);   // error
}

【讨论】:

  • 感谢您的直观测试.. 但它没有回答问题。问题是为什么方法的调用 fail 而method2 passes
  • 从分析来看,调用method2 是不明确的,因为缺少最具体的方法。因此这是一个编译器错误。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2015-03-08
  • 1970-01-01
  • 2011-12-19
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多