【问题标题】:Groovy method overloading: selection of method prefers interfaces over subclasses?Groovy 方法重载:方法的选择更喜欢接口而不是子类?
【发布时间】:2015-06-11 00:46:31
【问题描述】:

你好 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 的距离(经过一些额外的移动,比如对对象参数类型的惩罚)然后与签名中带有 only 接口的两个添加方法的距离 2 进行比较,这导致方法#add(C c) 未被选择/考虑。发生异常是因为,现在确实有 2 个方法(#add(A a) 和 #add(B b))具有相同的距离,并且运行时无法知道选择哪个方法。

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

【问题讨论】:

标签: java groovy overloading


【解决方案1】:

这听起来像是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 {}

【讨论】:

  • 好的,谢谢你的例子。链接的错误是相关的,尽管在我们看来,并不完全相同:在其中,他们只讨论一个类实现多个接口时的问题。然而,在我们的例子中,我们认为这是一个错误,因为很明显,D 比任何接口类型的方法都“更接近”C。
  • 我有一个类似的问题:一个类有 3 个方法,一个是 Runnable,一个是 Callable,最后一个是 Closure。使用闭包调用方法时,使用 Runnable 方法。这是同一个问题吗?
  • @TobiasSchulte 我以前见过那个。我认为您需要使用 as Callable 进行闭包
  • 或者使用 groovy 扩展模块。我已经为有问题的图书馆做了这个:github.com/awaitility/awaitility/pull/46
猜你喜欢
  • 2012-06-11
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-04-15
  • 1970-01-01
  • 2012-01-17
  • 2016-09-24
  • 2011-05-13
相关资源
最近更新 更多