Groovy方法重载:选择方法更喜欢接口而不是子类?

时间:2014-09-25 11:30:28

标签: java groovy overloading

Hello Groovy& Java专家

我们遇到了一种特殊的Groovy行为,在我们看来,这种行为似乎是语言中的限制(或错误)。我们的长篇文章归结为这个问题:

  

Groovy中的方法选择是否故意更喜欢接口   方法重载正在进行时的子类化?

我们创建了一个简单的例子来说明这个案例:

interface A {}
interface B {}

class C implements A, B {}
class D extends C {}

class Foo {
    void add(A a) { System.out.println("A"); }
    void add(B b) { System.out.println("B"); }
    void add(C c) { System.out.println("C"); }
}

D d = new D();
new Foo().add(d);

我们所期望的是调用方法Foo#add(C c),但抛出以下异常:

groovy.lang.GroovyRuntimeException: Ambiguous method overloading for method Foo#add.
Cannot resolve which method to invoke for [class D] due to overlapping prototypes between: [interface A] {interface B]

这似乎出乎意料,因为Foo#add(C c)显然是最佳人选。因此,我们在Java中测试了这个确切的代码,并且它按预期工作:方法Foo#add(C c)被调用。

然后我们继续进一步调查并通过源代码进行调试。具体来说,有一种方法可以选择调用哪些方法:groovy.lang.MetaClassImpl#chooseMostSpecificParams

在那里,在所有3个(在我们的例子中)#add方法中计算距离 - 最后 - 在这里:org.codehaus.groovy.runtime.MetaClassHelper#calculateParameterDistance(java.lang.Class, org.codehaus.groovy.reflection.CachedClass)

然后算法连续增加距离。首先,因为在我们的例子中,参数d(D的实例)不是接口而不是基本类型,所以添加了距离17。 其次,检查类型C和D是否相同或者D是否继承自C。对于C高于D的每个继承级别,添加距离3。因此,我们最终的距离是20。

这个距离为20(在一些额外的移动之后,如对象参数类型的惩罚)然后比较两个添加方法的距离为2,其中只有 接口的签名,这导致方法#add(C c)未被选择/考虑。发生异常的原因是,现在确实有两种方法(#add(A a)和#add(B b))具有相同的距离,运行时无法知道选择哪种方法。

也许有人可以向我们解释为什么与Java相比,Groovy中的处理方式不同?

1 个答案:

答案 0 :(得分:2)

这听起来像this (unsolved) bug更具体的情况。我建议填写JIRA。


Groovy的方法选择在运行时使用multiple dispatch, or multimethods,因为它是一个带有可选类型的动态语言,而Java使用单一分派,其中将在编译时定义将被调用的方法。

以下代码适用于Java,但在Groovy中失败并出现断言错误:

public class SingleMult {
  public static void main(String[] args) {
    A a = new B();
    assert(new SingleMult().method(a) == "A");
  }

  String method(A a) { return "A"; }
  String method(B b) { return "B"; }
}

interface A {}

class B implements A {}