Демо scala код:
trait A {
val a = 3
val b = a + 2
}
trait B extends A {
override val a = 10
}
object X extends B
println(X.b)
Он печатает значение: 2
, почему это не 5
или 12
?
Демо scala код:
trait A {
val a = 3
val b = a + 2
}
trait B extends A {
override val a = 10
}
object X extends B
println(X.b)
Он печатает значение: 2
, почему это не 5
или 12
?
Чтобы ответить на вопрос, почему:
В Scala, когда вы пишете
class A {
val a = 2
}
Значение инициализируется в конструкторе класса (такое же поведение относится к объектам и объектам). Кроме того, суперклассы инициализируются перед подклассами. Это приводит к следующему поведению для вашего случая использования:
B
(зарезервирована память), с двумя переменными a
и B
, значение которых равно 0. Теперь вызывается конструктор a
. Поскольку a
перезаписывается в подклассе и из-за динамического связывания Scalas, он не присваивается с 2, а со значением подкласса. Вы хотите, чтобы это было 10, но поскольку это назначение происходит в конструкторе B
(который еще не вызывается), присваивается значение по умолчанию 0. Теперь назначается B
. Поскольку он не перезаписывается, выбирается значение a+2
, где a
равно 0. Поскольку конструктор a
завершен здесь, может быть вызван конструктор B
, который присваивает значение 10 на a
.
Следовательно, a
равно 10 и B
равно 2.
Чтобы ответить, что делать с ошибкой этого поведения:
Не используйте vals, если вы не совсем понимаете проблемы, которые могут возникнуть. Вместо этого используйте defs или lazy vals, там значения не инициализируются в конструкторе класса и поэтому могут быть легко перезаписаны. Если вам абсолютно нужен val в признаке, сделайте его окончательным
Можно пометить переменную как инициализацию, независимую для подкласса, которая может быть выполнена с помощью var a: Type = _
. Это говорит компилятору не инициализировать эту переменную в конструкторе определяющего класса (но означает, что значение должно оставаться изменчивым). Затем он может быть легко назначен в подклассе. Это становится важным, когда вызывается в конструкторе суперкласса как метод, который инициализирует var:
class A {
f()
def f() = ()
}
class B extends A {
// don't initialize this var with anything else here or
// the later assignment will be overwritten
var b: Int = _
override def f() =
b = 5
}
new B().b // prints 5