Утверждения в абстрактном суперклассе scala, создающем NPE

Следующий код при вводе в REPL

abstract class A { val aSet: Set[Int]; require(aSet.contains(3)) }

class B extends A { val aSet = Set(4,5,6) }

new B()

дает исключение с нулевой точкой, а не инвариантный сбой.

Какой была бы лучшая идиома для решения этой проблемы?


Похожие вопросы:

Контракты кода: инварианты в абстрактном классе

Частный конструктор в абстрактном классе Scala?

а также онлайн-комментарии: https://gist.github.com/jkpl/4932e8730c1810261381851b13dfd29d


person A. D    schedule 08.08.2018    source источник
comment
Это связано с порядком вызова конструкторов. Не уверен, что это может сработать для вас, но вы можете обойти это, сделав aSet def (как в abstract class, так и в его реализации).   -  person stefanobaghino    schedule 08.08.2018


Ответы (1)


Когда вы объявляете val, происходит несколько вещей:

  1. Компилятор следит за тем, чтобы для переменной было выделено достаточно места при инициализации экземпляра класса.
  2. Метод доступа создан
  3. Создаются инициализаторы, которые устанавливают начальные значения переменных.

Ваш код

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

Итак, происходит следующее:

  1. Память для aSet_A и aSet_B выделена и установлена ​​на null.
  2. Запускается инициализатор A.
  3. require на aSet.contains(3) вызывается
  4. Поскольку aSet переопределяется в B, возвращается aSet_B.
  5. Поскольку 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:

Список сопутствующих вопросов:

  1. Почему реализация абстрактного метода с использованием val и вызов из суперкласса в выражении val возвращает NullPointerException
  2. Родительский код переопределенного значения запущен, но значение не присвоено родительскому элементу
person Andrey Tyukin    schedule 08.08.2018