在 class 的情况下如何使用另一个字段初始化一个字段?

How to initialize one field using another in case class?

假设我有一个案例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))}}

你可以重载案例的构造函数class

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)

只是解释如何从伴随对象重载 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)

您也可以使用多参数列表构造函数定义默认值,但很难使用这种情况 class(没有 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)

技术方案(不要)

在 case classes 的当前定义中,您可以覆盖 case class 的构造函数及其伴随对象的 apply 方法,如 中所述。

你会得到这样的东西:

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 和一些参数元组构造一个案例 class 然后使用 unapply 解构它时,我希望得到我放入的原始元组apply(忽略柯里化和选项类型)。

但在这种情况下,这个 属性 不再成立了:

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

使用unapply解构用于模式匹配(match{ case ... => ...)。这是 case classes.

的常见用例

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

重新表述问题(我的建议)

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

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"

关于 case classes 的基本属性未被此代码更改。