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



HelloGroovy&Java专家

我们遇到了一种特殊的Groovy行为,在我们看来,它就像是语言中的一个限制(或bug)。我们的长文可以归结为这个问题:

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。

然后,对于签名中只有接口的两个add方法,20的距离(在一些额外的偏移(如对象参数类型的惩罚)之后)与2的距离进行比较,这导致没有选择/考虑方法#add(C C)。出现异常是因为,实际上,现在有两个方法(#add(A A)和#add(B B))具有相同的距离,运行时不知道该选择哪种方法。

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

这听起来像是这个(未解决的)bug的一个更具体的例子。我建议填写一份关于它的JIRA。


Groovy的方法选择在运行时使用多分派或多方法,因为它是一种具有可选类型的动态语言,而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 {}

最新更新