5

我正在尝试在 Java 库jOOQ中调用此处set记录的此方法,并带有签名:

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

这条 Scala 行是个问题:

 .set(table.MODIFIED_BY, userId)  

MODIFIED_BY是一个Field<Integer>代表表格列。userIdInt。有一个从toPredef的隐式转换,那么为什么不使用它呢?我明白了: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)

更新 - 关于维尼修斯的例子

与其尝试在注释中解释这一点,不如在这里演示当您使用具有协变参数的类型时不会调用隐式转换,例如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. 相反,它选择了 a T,因为它是和test[T]之间的公共基类。当您使用它时,它会选择,但它会令人困惑,因为列表中的运行时表示是. 所以看起来它使用了隐式转换,但它没有。您可以通过打开 Scala 提示符来验证...StringFooIntIntegerAnyIntInteger

scala> :type StackOver1.test(List(new java.lang.Integer(1)), 2)
List[Any]
4

1 回答 1

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. Erasure -> 编译时泛型的类型将通过擦除消失。编译器将使用 Scala 将视为 Any 的 Object。然而 ArrayList[Integer] 不是 ArrayList[Any],即使 Integer 是 any。与 TableField[gen.tables.records.DocRecord,Integer] 不是 Field[Any] 的方式相同。

  2. 类型推断机制 -> 它将确定 T 应该是什么类型,并为此使用传递的类型的交集支配者(在我们的例子中是第一个共同祖先)。Scala Language Spec的第 36 页,在我们上面的示例中将导致使用 Any。

  3. 隐式转换 -> 这是最后一步,如果有某种类型要转换为另一种类型,则会调用它,但由于参数的类型被确定为第一个共同祖先,因此无需转换,我们将如果我们不强制类型 T,就永远不会进行隐式转换。

显示如何使用共同祖先来确定 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,感谢您质疑我的答案,我在这个过程中学到了很多东西。

于 2013-09-23T15:32:39.310 回答