2010-01-29 30 views
7

任何人都可以解释下面的编译错误?有趣的是,如果我将get()方法的返回类型更改为String,代码编译就好了。请注意,thenReturn方法有两个重载:一个一元方法和一个至少需要一个参数的可变参数方法。在我看来,如果调用在这里不明确,那么它总是含糊不清。Scala 2.7.7编译器/解释器中的伪歧义引用错误?

更重要的是,有没有什么办法来解决歧义?

import org.scalatest.mock.MockitoSugar 
import org.mockito.Mockito._ 

trait Thing { 
    def get(): java.lang.Object 
} 

new MockitoSugar { 
    val t = mock[Thing] 

    when(t.get()).thenReturn("a") 
} 

error: ambiguous reference to overloaded definition, both method thenReturn in trait OngoingStubbing of type
java.lang.Object,java.lang.Object*)org.mockito.stubbing.OngoingStubbing[java.lang.Object] and method thenReturn in trait OngoingStubbing of type (java.lang.Object)org.mockito.stubbing.OngoingStubbing[java.lang.Object] match argument types (java.lang.String) when(t.get()).thenReturn("a")

+0

我打开了一张关于此的票据,因为我发现斯卡拉甚至没有与自己保持一致。门票https://lampsvn.epfl.ch/trac/scala/ticket/2991。 – 2010-01-29 12:01:04

+0

门票被关闭为无效,现在有关于正在发生什么的解释,我将复制到我自己的答案中。目前,我认为这个变化的机会不大。 – 2010-02-03 13:09:31

回答

9

嗯,这是不明确的。我认为Java语义允许它,并且可能需要一个请求在Java语义中应用Scala的票证。

歧义的来源是:可变参数可能会收到任意数量的参数,包括0.所以,当你写thenReturn("a"),你的意思是调用thenReturn接收单个参数,或者你的意思是调用thenReturn接收一个对象加可变参数,将0个参数传递给可变参数?

现在,这是什么样的事情发生,斯卡拉试图找出哪一种方法是“更具体”。任何有兴趣的细节应该查找,在Scala的规范,但这里是在这种特殊情况下,会发生什么情况的说明:

object t { 
    def f(x: AnyRef) = 1 // A 
    def f(x: AnyRef, xs: AnyRef*) = 2 // B 
} 

if you call f("foo") , both A and B are applicable. Which one is more specific?

  • it is possible to call B with parameters of type (AnyRef) , so A is as specific as B.
  • it is possible to call A with parameters of type (AnyRef, Seq[AnyRef]) thanks to tuple conversion, Tuple2[AnyRef, Seq[AnyRef]] conforms to AnyRef . So B is as specific as A. Since both are as specific as the other, the reference to f is ambiguous.

至于“元组转换”的东西,它的一个斯卡拉大多数模糊的语法糖。如果您拨打一个电话f(a, b),其中ab有类型AB,并没有f接受(A, B)但有一个f它接受(Tuple2(A, B)),那么参数(a, b)将被转换成一个元组。

例如:

scala> def f(t: Tuple2[Int, Int]) = t._1 + t._2 
f: (t: (Int, Int))Int 

scala> f(1,2) 
res0: Int = 3 

现在,没有元组转换时会被thenReturn("a")呼吁。这不是问题。问题是,考虑到元组转换是可能的,thenReturn这两个版本都不是特定的,因为传递给一个参数的任何参数都可以传递给另一个参数。

6

好吧,我想通了,如何解决歧义(似乎有点明显回想起来):

when(t.get()).thenReturn("a", Array[Object](): _*) 

如安德烈亚斯指出,如果不明确的方法需要一个空引用,而不是一个空阵列,你可以使用类似

v.overloadedMethod(arg0, null.asInstanceOf[Array[Object]]: _*) 

来解决歧义。

+1

这是实际的答案(尽管Daniel Sobral的当然非常丰富)。虽然,我发现我必须提供返回类型的数组,而不仅仅是Array [Object]。 – gladed 2012-03-12 00:56:55

4

如果你看一下标准库API,你会看到这个问题是这样处理的:

def meth(t1: Thing): OtherThing = { ... } 
def meth(t1: Thing, t2: Thing, ts: Thing*): OtherThing = { ... } 

通过这样做,没有呼叫(至少有一个件事参数)是不喜欢Array[Thing](): _*额外的绒毛暧昧。

+0

这似乎是一个更好的写法。不幸的是,'thenReturn'是在第三方Java库(Mockito)中定义的。正如Daniel指出的,Java解决了non-varargs方法的含糊性,所以我甚至不能称它为库中的一个bug。 – 2010-01-29 19:27:07

+0

现在我明白了这个问题,当传递两个参数时,它很可能容易模棱两可。 – 2010-02-03 13:08:27

3

我有一个类似的问题,使用椭圆形(oval.sf.net)试图调用它的validate() - 方法。

椭圆定义2的validate()的方法:

public List<ConstraintViolation> validate(final Object validatedObject) 
public List<ConstraintViolation> validate(final Object validatedObject, final String... profiles) 

从Scala的尝试这样的: validator.validate(value) 产生以下编译器错误:

both method validate in class Validator of type (x$1: Any,x$2: <repeated...>[java.lang.String])java.util.List[net.sf.oval.ConstraintViolation]               
and method validate in class Validator of type (x$1: Any)java.util.List[net.sf.oval.ConstraintViolation]                        
match argument types (T)                                             
     var violations = validator.validate(entity);                                      

椭圆需要可变参数参数为空,而不是空阵列,所以我终于得到它与此工作:

validator.validate(value, null.asInstanceOf[Array[String]]: _*)

+0

感谢您加入您的案例,Andreas。我把它记录在Daniel的trac票上(https://lampsvn.epfl.ch/trac/scala/ticket/2991)。 – 2010-02-01 22:07:53

6

在的Mockito的特定情况下,它可以使用专为使用无效的方法替代API方法:

doReturn("a").when(t).get() 

笨重,但它必须做,马丁等人不要”为了支持Java的可变参数,似乎可能会损害Scala。