如何在案例类中使用另一个字段初始化一个字段?

时间:2015-01-27 20:23:34

标签: scala case-class

假设我有一个类似

的案例类
case class Person(fname:String, lname:String, nickName:Option[String] = None)

在创建像Person这样的实例(" John"," Doe")时,我希望nickName自动分配给fname,如果没有给出。例如:

val p1 = Person("John", "Doe")
p1.nickName.get == "John"

val p2 = Person("Jane", "Doe", "Joe")
p2.nickName.get == "Joe"

如何实现从另一个字段自动分配一个字段?

在repl中尝试以下解决方案。我认为这与repl

有关
scala> case class Person(fname: String, lname:String, nickName:Option[String])
defined class Person

scala> object Person { def apply(fname:String, lname:String):Person = {Person(fname, lname, Some(fname))}}
  console:9: error: too many arguments for method apply: (fname: String, lname: String)Person in object Person
   object Person { def apply(fname:String, lname:String):Person = {Person(fname, lname, Some(fname))}}

3 个答案:

答案 0 :(得分:7)

您可以重载案例类的构造函数

case class Foo(bar: Int, baz: Int) {
     def this(bar: Int) = this(bar, 0)
}
new Foo(1, 2)
new Foo(1)

因此,如果nickName为none,您可以检查案例。

你也可以用同样的方式重载它的apply方法。这样,你可以使用

Foo(1,2)
Foo(1)

答案 1 :(得分:4)

技术解决方案(不要)

在案例类的当前定义中,您可以覆盖案例类的构造函数及其伴随对象的apply方法,如the answer of Facundo Fabre中所述。

你会得到这样的东西:

object Person {
  def apply(fname:String, lname:String): Person = Person(fname, lname, fname)
}

case class Person(fname:String, lname:String, nickName: String) {
  def this(fname:String, lname:String) = this(fname, lname, fname)
}

这是技术上正确且非常聪明的编码。但根据我的口味,它有点过于聪明,因为它打破了一个重要的属性:

CaseClass.unapply(CaseClass.apply(x1,x2,x3)) == (x1,x2,x3)

换句话说:当我使用apply构造一个带有一些参数元组的案例类,然后使用unapply解构它时,我希望得到我放入apply的原始元组(忽略currying和选项类型)。

但在这种情况下,此属性不再适用:

Person.unapply(Person("John", "Smith"))
// result: Option[(String, String, String)] = Some((John,Smith,John))

使用unapply进行解构用于模式匹配(match{ case ... => ...)。这是案例类的常见用例。

因此,虽然代码非常聪明,但它可能会混淆其他人并破坏其代码所依赖的属性。

重新解释问题(我的建议)

当需要过于聪明的代码时,重新思考一个人试图解决的问题通常是个好主意。在这种情况下,我建议区分用户选择的昵称和系统分配给用户的昵称。在这种情况下,我会建立一个这样的案例类:

case class NickedPerson(fname : String, lname : String, chosenNick : Option[String] = None) {
  val nick = chosenNick.getOrElse(fname)
}

然后,您可以使用字段nick来访问计算的昵称,或者如果您想知道用户是否提供了昵称,请使用chosenNick

NickedPerson("John", "Smith").nick
// result: String = "John"
NickedPerson("John", "Smith", Some("Agent")).nick
// result: String = "Agent"

此代码不会更改有关案例类的基本属性。

答案 2 :(得分:2)

仅解释如何从伴侣对象重载apply(除了@Facundo Fabre答案):

scala> :paste
// Entering paste mode (ctrl-D to finish)

object Person {
   def apply(fname:String, lname:String): Person = Person(fname, lname, fname)
}
case class Person(fname:String, lname:String, nickName: String)


// Exiting paste mode, now interpreting.

defined object Person
defined class Person

scala> Person("aaa", "bbb")
res24: Person = Person(aaa,bbb,aaa)

scala> Person("aaa", "bbb", "ccc")
res25: Person = Person(aaa,bbb,ccc)

你也可以使用多参数列表构造函数定义默认值,但是很难使用这样的案例类(没有toString和模式匹配最后一个参数),所以不推荐(但如果你想要方法的相似之处,它会很好):

scala> case class Person(fname:String, lname:String)(val nickName: String = fname)
defined class Person

另一个有趣的解决方案(只是为了玩),我不建议在实际代码中使用它:

scala> case class Person(fname:String, lname:String, var nickName: String = null){nickName = Option(nickName).getOrElse(fname)}
defined class Person

scala> Person("aaa", "bbb")
res32: Person = Person(aaa,bbb,aaa)