Assertions in abstract superclass scala creating N

2019-02-26 06:19发布

问题:

The following code, when entered in REPL

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

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

new B()

gives a null point exception, rather than an invariant failure.

What would be the best idiom to solve this problem?


Similar questions:

Code Contracts: Invariants in abstract class

Private constructor in abstract class Scala?

and also online commentary: https://gist.github.com/jkpl/4932e8730c1810261381851b13dfd29d

回答1:

When you declare a val, several things happen:

  1. The compiler makes sure that enough space is allocated for the variable when an instance of the class is initialized
  2. An accessor-method is created
  3. Initializers that setup the initial values of the variables are created.

Your code

abstract class A { val aSet: Set[Int]; require(aSet.contains(3)) }
class B extends A { val aSet = Set(4,5,6) }
new B()

is roughly equivalent to

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

so, the following happens:

  1. Memory for aSet_A and aSet_B is allocated and set to null.
  2. Initializer of A is run.
  3. require on aSet.contains(3) is invoked
  4. Since aSet is overridden in B, it returns aSet_B.
  5. Since aSet_B is null, an NPE is thrown.

To avoid this, you can implement aSet as a lazy variable:

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

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

new B()

This throws the requirement failed exception:

java.lang.IllegalArgumentException: requirement failed

Obligatory link to Scala's FAQ:

  • Why is my abstract or overridden val null?

List of related questions:

  1. Why does implement abstract method using val and call from superclass in val expression return NullPointerException
  2. Overridden value parent code is run but value is not assigned at parent