【问题标题】:Why can auxiliary constructors in Scala only consist of a single call to another constructor?为什么 Scala 中的辅助构造函数只能包含对另一个构造函数的一次调用?
【发布时间】:2013-01-08 03:58:46
【问题描述】:

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

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)
  }
}

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

【问题讨论】:

标签: scala


【解决方案1】:

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

正如 Scala 中的编程 中所述,ch. 6.7:

在 Scala 中,每个辅助构造函数都必须调用另一个构造函数 与它的第一个动作相同的类。换句话说,每个语句中的第一个语句 每个 Scala 类中的辅助构造函数都将具有this(. . . ) 的形式。 调用的构造函数是主构造函数(如Rational 例如),或另一个在文本之前的辅助构造函数 调用构造函数。该规则的最终效果是每次构造函数调用 在 Scala 中,最终会调用 班级。因此,主构造函数是类的单一入口点。

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

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

此外,您的具体情况很尴尬,因为您有两个构造函数参数,并且 - 尽管可以从函数返回元组 - 然后这个返回的元组不被接受作为主构造函数的参数列表。 For ordinary functions, you can use 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")
  }

}

这至少可行,即使它有点复杂。

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

更新

正如@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")
}

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

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

init
Wobble wibble
w: Wobble = Wobble@47c130

【讨论】:

  • Scala 有一个内置的 init 方法,它被称为 apply 并且更适合这里。
  • @sschaef,你是对的,这种重构消除了辅助构造函数并使代码更加简洁。我保留我的原始代码作为 OP 的直接答案,但也向apply 添加了一个示例。
  • @sschaef 只要您不需要在纯 Java 中使用代码,这就是事实。那么 new Wobble(list) 可能看起来比 Wobble.apply(list) 更好
猜你喜欢
  • 1970-01-01
  • 2014-08-20
  • 1970-01-01
  • 2019-01-10
  • 2015-01-28
  • 1970-01-01
  • 1970-01-01
  • 2011-03-24
  • 1970-01-01
相关资源
最近更新 更多