为什么Scala中的辅助构造函数只能由对另一个构造函数的单个调用组成?

Phi*_*sky 13 scala

我认为我看到了以这样一种方式定义辅助构造函数的优点,即主构造函数是类的单独入口点.但为什么我不能这样做呢?

class Wibble(foo: Int, bar: String) {
  def this(baz: List[Any]) = {
    val bazLength = baz.length
    val someText = "baz length is " ++ bazLength.toString
    this(bazLength, someText)
  }
}
Run Code Online (Sandbox Code Playgroud)

它可能是一种保证辅助构造函数没有副作用和/或不能提前返回的方法吗?

Pét*_*rök 21

辅助构造函数可以包含多个另一个构造函数的调用,但它们的第一个语句必须是所谓的调用.

正如在Scala编程中所解释的那样.6.7:

在Scala中,每个辅助构造函数必须调用与其第一个操作相同的类的另一个构造函数.换句话说,每个Scala类中每个辅助构造函数中的第一个语句都将具有该表单this(. . . ).被调用的构造函数是主构造函数(如Rational 示例中所示),或者是在调用构造函数之前以文本方式出现的另一个辅助构造函数.此规则的最终效果是Scala中的每个构造函数调用最终都会最终调用该类的主构造函数.因此,主要构造函数是类的单个入口点.

如果您熟悉Java,您可能想知道为什么Scala的构造函数规则比Java更严格.在Java中,构造函数必须调用同一个类的另一个构造函数,或者直接调用超类的构造函数作为其第一个操作.在Scala类中,只有主构造函数可以调用超类构造函数.Scala增加的限制实际上是一种设计权衡,需要付出代价来换取Scala构造函数与Java相比更加简洁和简单.

就像在Java中一样,通过在主构造函数调用单独的方法之前提取要执行的代码,可以绕过此限制.在Scala中它比Java更棘手,因为显然你需要将这个辅助方法移动到伴随对象中,以便允许从构造函数中调用它.

此外,你的具体情况很尴尬,因为你有两个构造函数参数 - 尽管一个可以从一个函数返回元组 - 然后这个返回的元组不被接受为主构造函数的参数列表.对于普通函数,您可以使用tupled,但是,这似乎对构造函数不起作用.解决方法是添加另一个辅助构造函数:

object Wibble {

  private def init(baz: List[Any]): (Int, String) = {
    val bazLength = baz.length
    val someText = "baz length is " ++ bazLength.toString
    println("init")
    (bazLength, someText)
  }
}

class Wibble(foo: Int, bar: String) {

  println("Wibble wobble")

  def this(t: (Int, String)) = {
    this(t._1, t._2)
    println("You can execute more code here")
  }

  def this(baz: List[Any]) = {
    this(Wibble.init(baz))
    println("You can also execute some code here")
  }

}
Run Code Online (Sandbox Code Playgroud)

这至少有效,即使它有点复杂.

scala> val w = new Wibble(List(1, 2, 3))

init
Wibble wobble
You can execute more code here
You can also execute some code here
w: Wibble = Wibble@b6e385
Run Code Online (Sandbox Code Playgroud)

更新

正如@ sschaef在他的评论中指出的那样,可以使用随播对象中的工厂方法简化:

object Wobble {

  def apply(baz: List[Any]): Wobble = {
    val bazLength = baz.length
    val someText = "baz length is " ++ bazLength.toString
    println("init")
    new Wobble(bazLength, someText)
  }
}

class Wobble(foo: Int, bar: String) {
  println("Wobble wibble")
}
Run Code Online (Sandbox Code Playgroud)

因此,我们不再需要new创建对象:

scala>  val w = Wobble(List(1, 2, 3))

init
Wobble wibble
w: Wobble = Wobble@47c130
Run Code Online (Sandbox Code Playgroud)