Когда вы объявляете val
, происходит несколько вещей:
- Компилятор следит за тем, чтобы для переменной было выделено достаточно места при инициализации экземпляра класса.
- Метод доступа создан
- Создаются инициализаторы, которые устанавливают начальные значения переменных.
Ваш код
abstract class A { val aSet: Set[Int]; require(aSet.contains(3)) }
class B extends A { val aSet = Set(4,5,6) }
new B()
примерно эквивалентно
abstract class A {
private var aSet_A: Set[Int] = null
def aSet: Set[Int] = aSet_A
require(aSet.contains(3))
}
class B extends A {
private var aSet_B: Set[Int] = Set(4,5,6)
override def aSet: Set[Int] = aSet_B
}
new B()
Итак, происходит следующее:
- Память для
aSet_A
и aSet_B
выделена и установлена на null
.
- Запускается инициализатор
A
.
require
на aSet.contains(3)
вызывается
- Поскольку
aSet
переопределяется в B
, возвращается aSet_B
.
- Поскольку
aSet_B
равно null
, выбрасывается NPE.
Чтобы избежать этого, вы можете реализовать aSet
как ленивую переменную:
abstract class A {
def aSet: Set[Int]
require(aSet.contains(3))
}
class B extends A {
lazy val aSet = Set(4,5,6)
}
new B()
Это вызывает исключение requirement failed
:
java.lang.IllegalArgumentException: requirement failed
Обязательная ссылка на FAQ по Scala:
Список сопутствующих вопросов:
- Почему реализация абстрактного метода с использованием val и вызов из суперкласса в выражении val возвращает NullPointerException
- Родительский код переопределенного значения запущен, но значение не присвоено родительскому элементу
person
Andrey Tyukin
schedule
08.08.2018
aSet
def
(как вabstract class
, так и в его реализации). - person stefanobaghino   schedule 08.08.2018