Я думаю, что вижу преимущество в определении вспомогательных конструкторов таким образом, чтобы основной конструктор был единственной точкой входа в класс. Но почему я не могу сделать что-то подобное?

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

Может быть, это способ гарантировать, что вспомогательный конструктор не имеет побочных эффектов и / или не может вернуться раньше?

14
Phil Darnowsky 8 Янв 2013 в 07:58
2
Вероятно, это наследие java: stackoverflow.com/questions/508639/…
 – 
Denis Tulskiy
8 Янв 2013 в 08:06
1
Заголовок и ваше понимание, вероятно, немного неверны. Перегрузка конструктора Scala? указывает, что первым действием должен быть предварительно определенный конструктор, но после этого у вас могут быть строки.
 – 
Karthik T
8 Янв 2013 в 08:07

1 ответ

Лучший ответ

Вспомогательные конструкторы могут содержать более одного вызова другого конструктора, но их первый оператор должен называться вызовом.

Как объясняется в Программирование на Scala , гл. 6.7:

В Scala каждый вспомогательный конструктор должен вызывать другой конструктор того же класса, что и его первое действие. Другими словами, первое утверждение в каждом вспомогательный конструктор в каждом классе Scala будет иметь вид this(. . . ). Вызванный конструктор является либо основным конструктором (как в Rational) example) или другой вспомогательный конструктор, который идет текстуально перед вызывающий конструктор. Чистый эффект этого правила заключается в том, что каждый вызов конструктора в 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")
  }

}

Это, по крайней мере, работает, даже если это немного сложно.

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
23
Community 23 Май 2017 в 15:32
1
Scala имеет встроенный метод инициализации, он называется apply и здесь гораздо лучше подходит.
 – 
kiritsuku
8 Янв 2013 в 13:24
@sschaef, вы правы, этот рефакторинг устраняет вспомогательные конструкторы и делает код намного чище. Я сохраняю свой исходный код как прямой ответ на OP, но также добавил пример в apply.
 – 
Péter Török
8 Янв 2013 в 13:39
1
Это верно до тех пор, пока вам не нужно использовать код на чистой Java. Тогда новый Wobble (список) может выглядеть лучше, чем Wobble.apply (list)
 – 
Piohen
24 Июл 2013 в 19:32