为什么Scala不会在这里使用隐式转换?

时间:2013-09-23 14:24:04

标签: scala implicit jooq type-mismatch

我正在尝试在Java库here中使用签名调用此set方法记录jOOQ

<T> ... set(Field<T> field, T value)

此Scala系列存在问题:

 .set(table.MODIFIED_BY, userId)  

MODIFIED_BY是表示表格列的Field<Integer>userIdIntPredef隐式转换为IntInteger,为什么不使用它?我明白了:

type mismatch; found: org.jooq.TableField[gen.tables.records.DocRecord,Integer]    
            required: org.jooq.Field[Any] 
Note: Integer <: Any 
(and org.jooq.TableField[gen.tables.records.DocRecord,Integer] <: 
     org.jooq.Field[Integer]), but Java-defined trait Field is invariant in type T. 
You may wish to investigate a wildcard type such as `_ <: Any`. (SLS 3.2.10)

更新 - 关于Vinicius的示例

不是试图在注释中解释这一点,而是演示当您使用具有协变参数的类型(例如List[+T])时,不会调用隐式转换。假设我把这段代码放在一个文件中,编译并运行它......

case class Foo(str: String)

object StackOver1 extends App {

  implicit def str2Foo(s: String): Foo = { 
    println("In str2Foo.")
    new Foo(s)
  }

  def test[T](xs: List[T], x: T): List[T] = {
    println("test " + x.getClass)
    xs
  }

  val foo1 = new Foo("foo1")
  test(List(foo1), "abc")
}

您会看到它调用test,但绝不会从String“abc”隐式转换为Foo。相反,它为T选择test[T] String,这是FooInt之间的公共基类。当您使用IntegerAny时,它会选择Int,但这会令人困惑,因为列表中Integer的运行时表示形式为scala> :type StackOver1.test(List(new java.lang.Integer(1)), 2) List[Any] 。所以看起来它使用了隐式转换,但事实并非如此。您可以通过打开Scala提示进行验证...

{{1}}

1 个答案:

答案 0 :(得分:2)

我对jOOQ一无所知,但我认为问题在于Scala不能很好地理解java泛型。尝试:

scala> def test[T](a : java.util.ArrayList[T], b: T) = {  println(a,b) }
scala> val a = new java.util.ArrayList[Integer]()
scala> val b = 12
scala> test(a,b)
<console>:11: error: type mismatch;
 found   : java.util.ArrayList[Integer]
 required: java.util.ArrayList[Any]
Note: Integer <: Any, but Java-defined class ArrayList is invariant in type E.
You may wish to investigate a wildcard type such as `_ <: Any`. (SLS 3.2.10)
              test(a,b)

听起来很熟悉?

要修复,只需告知类型T调用方法:test[Integer](a,b)正常工作。

修改

这里涉及到一些事情:

  1. 删除 - &gt;编译时,通用的类型将通过擦除消失。编译器将使用Scala将被视为Any的Object。但是,ArrayList [Integer]不是ArrayList [Any],即使Integer是any。与TableField [gen.tables.records.DocRecord,Integer]不是Field [Any]的方式相同。

  2. 类型推断机制 - &gt;它将弄清楚T应该是什么类型,并且要做到这一点,它将使用传递的类型的交集支配者(在我们的例子中是第一个共同的祖先)。 Scala Language Spec的第36页,在上面的示例中将使用Any。

  3. 隐式转换 - &gt;它是最后一步,并且如果有某种类型被转换为另一种类型将被调用,但由于参数的类型被确定为第一个共同的祖先,所以不需要转换,我们永远不会有隐含的转换,如果我们不强制类型T。

  4. 显示共同祖先如何用于确定T的示例:

    scala> def test[T](a: T, b: T): T = a
    scala> class Foo
    scala> class Boo extends Foo
    scala> test(new Boo,new Foo)
    res2: Foo = Boo@139c2a6
    scala> test(new Boo,new Boo)
    res3: Boo = Boo@141c803
    scala> class Coo extends Foo
    scala> test(new Boo,new Coo)
    res4: Foo = Boo@aafc83
    scala> test(new Boo,"qsasad")
    res5: Object = Boo@16989d8
    

    总结一下,隐式方法不会被调用,因为类型推断机制在获取参数之前确定了类型,并且因为它使用了共同的祖先,所以不需要隐式转换。

    您的代码由于擦除机制而产生错误,该错误机制会随着确定参数类型正确的类型信息而消失。

    @RobN,感谢您质疑我的回答,我从这个过程中学到了很多东西。