如何规避 Scala case class 22 字段的限制?

55

Scala的Case Class在构造函数中有22个字段的限制。我想超过这个限制,是否有一种通过继承或组合来实现Case Class超过22个字段的方法?


是的,你说得对。在某些情况下,我使用隐式 case class,它使用的字段比实际类少。 - Phil
6个回答

54

更近期(在原帖发布六年后的2016年10月),来自Richard Dallaway的博客文章"Scala and 22"探讨了这个限制:

回到2014年,当Scala 2.11发布时,一个重要的限制被移除了:

Case classes with > 22 parameters are now allowed. 

话虽如此,Scala中仍然存在对于case class字段数量的限制,请参见https://stackoverflow.com/a/55498135/1586965

这可能会让你认为在Scala中没有22个限制,但事实并非如此。 该限制仍然存在于函数和元组中

Scala 2.11中引入的修复程序(PR 2305)已经移除了上述常见情况的限制:构建case class、字段访问(包括复制)和模式匹配(除极端情况外)。

它通过省略超过22个字段的case classes的unapplytupled来实现这一点。
换句话说,对于Function22Tuple22的限制仍然存在。

绕过限制(在Scala 2.11之后)

有两个常见的技巧可以绕过这个限制。

  • 第一个是使用嵌套元组。尽管元组不能包含超过22个元素,但每个元素本身都可以是一个元组。

  • 另一个常见技巧是使用异构列表(HLists),其中没有22个限制。

如果你想要使用case classes,最好使用shapeless HList实现。我们创建了Slickless库来使这更容易。特别是最近的mappedWith方法将shapeless的HLists和case classes之间进行转换。它看起来像这样:

import slick.driver.H2Driver.api._
import shapeless._
import slickless._

class LargeTable(tag: Tag) extends Table[Large](tag, "large") {
  def a = column[Int]("a")
  def b = column[Int]("b")
  def c = column[Int]("c")
  /* etc */
  def u = column[Int]("u")
  def v = column[Int]("v")
  def w = column[Int]("w")

  def * = (a :: b :: c :: /* etc */ :: u :: v :: w :: HNil)
    .mappedWith(Generic[Large])
}

Slickless 代码库中有一个包含26列的完整示例,请点击此处查看


31

这个问题将在Scala 2.11中得到解决。


1
Scala 2.11是救星! - soulmachine

24

构建一个普通类,其行为类似于 case 类。

我仍然使用 Scala 2.10.X,因为这是 Spark 最新支持的版本,在 Spark-SQL 中我经常使用 case 类。

对于具有超过 22 个字段的 case 类 的解决方法:

class Demo(val field1: String,
    val field2: Int,
    // .. and so on ..
    val field23: String)

extends Product 
//For Spark it has to be Serializable
with Serializable {
    def canEqual(that: Any) = that.isInstanceOf[Demo]

    def productArity = 23 // number of columns

    def productElement(idx: Int) = idx match {
        case 0 => field1
        case 1 => field2
        // .. and so on ..
        case 22 => field23
    }
}

1
扩展Product可以让你获得漂亮的迭代部分,但你不会得到复制方法。复制方法在case类中而不是Product特质中,因为它是由Scala编译器生成的(这样每个case类字段都可以强类型化)。 - Samer Adra
你在Spark中注册了哪个序列化器?我已经开始使用twitter.chill.avro.AvroSerializer.SpecificRecordBinarySerializer,但在这种情况下,我的类必须实现SpecificRecordBase,这又让我面临着22个字段的限制。 - Stuart

19

你的构造函数很复杂,但你可以将相关的值打包到一个case class中。

这样一来,即使你有

case class MyClass(street: String, city: String, state: String, zip: Integer)

你可以做到这件事

case class MyClass(address: Address)

还有其他选择:

  • 将项目分组为元组
  • 创建您自己的Function23特质(或任何其他名称)
  • 使用柯里化

更新:正如其他人所指出的,在发布Scala 2.11之后,这不再是一个问题--尽管我会犹豫使用“修复”一词。然而,这个所谓的“Catch 22”有时仍会出现在第三方Scala库中。


1

-5

当你有那么多的值时,通常意味着你的设计需要重新调整。

形成间歇性的案例类,然后聚合到更大的类中。这也使代码更容易理解、推理和维护。同时也可以避免你所遇到的问题。

例如,如果我想存储用户数据,我可能会这样做...

case class User(name: Name, email: String)
case class Name(first: String, last: String)

当然,如果你只有很少的东西,这是不必要的。但是,如果你有22个东西想塞进一个类中,你仍然需要进行这种间歇性的情况分类工作。


9
需要解析的是一个包含22个以上字段的json文件。使用诸如json4s之类的对象映射器需要一个具有所有字段的case类。 - Sohaib
这个答案是我想到的第一个,而且大体上是正确的。 - Dragonborn

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接