【问题标题】:Why won't Scala use implicit conversion here?为什么 Scala 不在这里使用隐式转换?
【发布时间】:2013-09-28 11:58:47
【问题描述】:

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

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

这条 Scala 行是个问题:

 .set(table.MODIFIED_BY, userId)  

MODIFIED_BY 是一个 Field&lt;Integer&gt; 代表表列。 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)

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

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

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

【问题讨论】:

    标签: scala implicit jooq type-mismatch


    【解决方案1】:

    我对 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,感谢您对我的回答提出质疑,我在这个过程中学到了很多东西。

    【讨论】:

    • 好的,谢谢。我确实了解 List 和 ArrayList 之间的差异差异,但不幸的是,在这种情况下添加 [Integer] 并不好。它会发生太多并使代码变得丑陋。我想知道为什么不使用隐式。
    • 如果我能在文档或语言规范中找到说明为什么它不考虑隐式的内容,我会接受这个答案。在考虑隐式之前,可能所有类型参数都已确定。如果不是这样,它可以使用ArrayList[Integer] 而不是ArrayList[Any]。你的例子不太正确......它不会“强制隐式转换”。在这种情况下令人困惑,因为 scala 在运行时使用 Integer,因为它是 Int,但您的测试函数只是得到一个 List[Any]。没有调用隐式转换函数。
    • @RobN,请帮助我改进您的答案。让我们看看我能不能说清楚。第一个示例重现了您的错误。编译器不知道如何将 (javageneric,Int) 匹配到 (javageneric,T)。 Scala 中的第二个显示了发生的隐式转换,注意 b 被转换为 java.lang.Integer。我不知道这是在文档中的哪个位置,我只是通过实验向您展示了。如果你有一个例子可以证明我错了,请做。
    • 我认为您输入了错误的代码示例。您将Class 类型的对象作为参数b 传递给test。没有发生转换。你有一个列表[任何]。但即使你通过了 12,仍然没有转换。我会在几分钟后用代码示例更新我的问题。
    • 更新了解释我的意思的问题。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2015-06-30
    • 1970-01-01
    • 1970-01-01
    • 2021-03-17
    • 1970-01-01
    • 1970-01-01
    • 2010-12-01
    相关资源
    最近更新 更多