Почему вспомогательный конструктор не видит импорт в классе?
Код ниже дает ошибку компиляции:
class Info(val x: String)
object Info {
val default = new Info("top")
}
case class Data(x: String) {
import Info.default
def this() = this(default.x)
}
Ошибка: (11, 23) не найден: значение по умолчанию def this() = this (default.x)
Почему символ default
не отображается в конструкторе, несмотря на импорт?
Дальнейшие эксперименты показывают, что это не только импорт. Замена строки импорта с помощью def
(или даже val
) не помогает, по-прежнему сохраняется ошибка:
def default = Info.default
Ответы
Ответ 1
Скопинг не работает так, как вы ожидали, из-за определения самоподтверждения конструктора (когда вторичные конструкторы ссылаются на первичный конструктор):
Подпись и вызов конструктора self конструктора определение проверяются по типу и оцениваются в области, которая находится в эффект в точке определения охватывающего класса, дополненный любые параметры входящего класса и любые ранние определения прилагаемого шаблона.
Другими словами, область действия для выражения default.x
- это область вне Data
.
Это сбивает с толку, потому что текстовое выражение похоже на то, что это выражение находится внутри класса.
Это правило связано с лексической областью, а не с порядком оценки.
Вам нужно переместить импорт за пределы определения класса.
Для удовольствия, область видимости слегка нарушена в следующем случае:
object Playroom {
def default = "playing"
case class Toy(squeezeMsg: String = default)
object Toy {
def default = "srsly"
}
}
object Test extends App {
import Playroom._
println(Toy())
println(new Toy())
}
который этот головоломка. Вообще говоря, более идиоматично (и надежно) использовать методы apply
на компаньоне.
Ответ 2
Это ведет себя по спецификациям. Тело класса является частью так называемого основного конструктора. вспомогательный конструктор (ы) должен начинаться с вызова первичного или другого вспомогательного устройства, определенного ранее. Предполагая, что у вас есть только одно вспомогательное средство, поскольку вызов первичного должно быть первым оператором в вашем вспомогательном устройстве, это означает, что во время этого первого утверждения у вас нет доступа к чему-либо, определенному внутри первичного. После завершения первого вызова вы можете получить доступ к любому члену и импорту, определенному в первичном.
В вашем примере проблема заключается в том, что импорт (import Info.default
) не отображается в первом утверждении вашего вспомогательного устройства. Если вы замените его на def this() = this(Info.default.x)
, он должен работать.
Пример:
Следующие компиляции отлично, так как вызов Constant
выполняется после вызова первичного.
class G(i: Int) {
val Constant = 1
// No argument auxiliary constructor
def this() = {
this(3) // Call to primary
println(Constant * 2)
}
}
Следующее не компилируется, поскольку вызов Constant
выполняется перед вызовом первичного, что означает, что Constant
еще не создан.
class G(i: Int) {
val Constant = 1
// No argument auxiliary constructor
def this() = {
this(Constant) // This will cause an error!
}
}
Решение:
Имеются ли какие-либо константы, которые вам нужны в сопутствующем объекте. Сопутствующий объект сначала инициализируется определением, поэтому вы сможете получить доступ к любым его членам внутри конструкторов вашего класса.
object G {
val Constant = 1
}
class G(i: Int) {
// No argument auxiliary constructor
def this() = {
this(G.Constant) // This now works!
}
}
Ответ 3
В scala любые поля, определенные в классе, недоступны до тех пор, пока не вызывается первичный конструктор. Для исправления обычно используется объект-компаньон с помощью метода apply
.