【问题标题】:Instantiating a case class from a list of parameters从参数列表中实例化案例类
【发布时间】:2011-05-16 12:17:58
【问题描述】:

给定:

case class Foo(a: Int, b: String, c: Double)

你可以说:

val params = Foo(1, "bar", 3.14).productIterator.toList

然后得到:

params: List[Any] = List(1, bar, 3.14)

有没有办法“倒退”并直接从这个列表中重新创建一个 Foo 对象,即:

Foo.createFromList(params)   // hypothetical

而不是写:

Foo(params(0).asInstanceOf[Int], params(1).asInstanceOf[String], params(2).asInstanceOf[Double])

编辑:似乎可以归结为能够将列表的元素作为参数发送给函数,而无需明确写出它们,例如:

def bar(a: Int, b: Int, c: Int) = //...
val list = List(1, 2, 3, 4, 5)
bar(list.take(3)) // hypothetical, instead of:
bar(list(0), list(1), list(2))

我希望能够做到:

bar(list.take(3): _*)

但这似乎不起作用。

编辑:基于即兴答案的解决方案,但直接调用构造函数而不是使用 apply 方法:

case class Foo(a: Int = 0, b: String = "bar", c: Double = 3.14) {
    val cs = this.getClass.getConstructors
    def createFromList(params: List[Any]) =
    cs(0).newInstance(params map { _.asInstanceOf[AnyRef] } : _*).asInstanceOf[Foo]
}

现在你可以这样做了:

scala> Foo().createFromList(List(4, "foo", 9.81))
res13: Foo = Foo(4,foo,9.81)

您也可以将创建方法重构为 trait:

trait Creatable[T <: Creatable[T]] {
    val cs = this.getClass.getConstructors
    def createFromList(params: List[Any]) =
        cs(0).newInstance(params map { _.asInstanceOf[AnyRef] } : _*).asInstanceOf[T]   
}

case class Bar(a: Int = 0, b: String = "bar", c: Double = 3.14) extends Creatable[Bar]

然后做例如:

scala> val bar = Bar()
bar: Bar = Bar(0,bar,3.14)

scala> bar == bar.createFromList(bar.productIterator.toList)
res11: Boolean = true

【问题讨论】:

  • 做一些快速基准测试,您会发现只需手动粘贴所有值,例如Foo(list(0), list(1), list(2)) 它快 10 倍。测试一个有 43 个值的案例类。

标签: scala


【解决方案1】:
scala> case class Foo(a: Int, b: String, c: Double)
defined class Foo

scala> val params = Foo(1, "bar", 3.14).productIterator.toList
params: List[Any] = List(1, bar, 3.14)

scala> Foo.getClass.getMethods.find(x => x.getName == "apply" && x.isBridge).get.invoke(Foo, params map (_.asInstanceOf[AnyRef]): _*).asInstanceOf[Foo]
res0: Foo = Foo(1,bar,3.14)

scala> Foo(1, "bar", 3.14) == res0
res1: Boolean = true

编辑:顺便说一句,到目前为止,仅用于提供元组作为参数的语法是:

scala> case class Foo(a: Int, b: String, c: Double)
defined class Foo

scala> Foo.tupled((1, "bar", 3.14))                
res0: Foo = Foo(1,bar,3.14)

【讨论】:

  • 这似乎是一个实际的解决方案。使用反射会产生多少开销? (而 isBridge 检查的意义何在?)
  • isBridge 只是一种选择正确方法的廉价方法。还有另一个同名的,它采用 (Int, String, Double) 而不是 (AnyRef, AnyRef, AnyRef) 并且该调用可能不会那么顺利。我无法以任何有意义的方式回答开销问题。 “一些。”
  • 也可以直接调用构造函数。我已经用一个例子编辑了我的问题。
【解决方案2】:

你可以像这样使用模式匹配:

params match {                                   
 case List(x:Int, y:String, d:Double) => Foo(x,y,d)
}

【讨论】:

    【解决方案3】:

    嗯,你当然可以用一个元组来做到这一点:

    (Foo _).tupled apply (1, bar, 3.14)
    

    但是对于A, B, C &lt;: S,没有真正的方法可以从List[S](A, B, C)。当然,HLists 可能有办法做到这一点

    【讨论】:

    • 这实际上行不通! Foo 伴侣单例实际上不是Function,它只是一个带有apply 方法的类。 (Foo.apply _).tupled 应该可以解决问题。
    • 不是吗? "public final class Foo$ extends scala.runtime.AbstractFunction3 implements scala.ScalaObject,java.io.Serializable"
    • @Kevin - 谢谢,我很确定我不必在自己的代码中明确声明 apply
    • @KevinWright 不涉及类型参数时:stackoverflow.com/questions/25345211/…
    【解决方案4】:

    另一个使用案例类伴生对象柯里化方法并完全忽略类型安全的衬线:)

    scala> case class Foo(a: Int, b: String, c: Double)
    defined class Foo
    
    scala> val lst = List(1, "bar", 3.14)
    lst: List[Any] = List(1, bar, 3.14)
    
    scala> val foo = lst.foldLeft(Foo.curried: Any){case (r, v) => r.asInstanceOf[Function[Any, _]](v) }.asInstanceOf[Foo]
    foo: Foo = Foo(1,bar,3.14)
    

    【讨论】:

      猜你喜欢
      • 2014-04-10
      • 1970-01-01
      • 2013-08-21
      • 2020-07-29
      • 2014-03-16
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-04-13
      相关资源
      最近更新 更多