Why can't my constructor call a method before every property is set?

← Classes and OOP · Ref: Q1396

An object cannot be used until it is fully constructed. Constructing an object runs the super constructor, then the type's property initialisers in declaration order, then the constructor body. Until every property has a value, calling a method on the object, or using 'this' as a value, can reach a property that is still unset. In Java that compiles and fails with a NullPointerException; EK9 rejects it with E08061.

WHEN THE OBJECT IS COMPLETE

In a constructor the object is complete once every property is set on the current path - counting what a this(...) call set, which is only what the constructor it delegates to sets (a 'default private' one sets nothing; see Q1404). After that it may call its own methods:

  Gauge()
    -> start as Integer
    reading: start
    doubled: twice()

Give a property a value at declaration ('doubled as Integer: 0') and it is already set when the constructor body starts.

PROPERTY INITIALISERS

A property initialiser runs before the constructor body, so the object is never complete there: it cannot call a method of the object - not even an inherited one, because an inherited method can call back into a method this type overrides. Compute the value with a function ('width as Integer: defaultWidth()') or take it from a constructor argument. Nor may an initialiser use 'this' at all - not as a value, not in a capture list, not even 'this.width' (write 'width'): see Q2180.

OPEN AND ABSTRACT TYPES

A subtype's own properties are only set after the super constructor returns. So the constructor of an open or abstract type can never call a method a subtype may override, even once its own properties are set. Make the helper private (a private method cannot be overridden) or compute the value with a function.

NOTHING EXISTS YET

The arguments of super(...) and this(...) are evaluated before any construction has happened, so they cannot call methods of the object either.

AN OBJECT THAT REFERS TO ITSELF

Storing the object in one of its own properties ('next: this') is not a use of it - nothing is read through it - so it is allowed before the object is complete and sets that property. Reading through that property is a use, and waits for every property to be set. See Q1471. A child that points back to its parent - a dynamic class capturing 'this', stored in the parent's own property - is the same kind of store: see Q2181 (one child), Q2182 (two children) and Q2183 (the mistakes).

See Q104 for uninitialised properties. See Q1397 for the order property initialisers run in.

Example

defines module qa.oop.construction.complete

  defines function

    defaultWidth()
      <- rtn as Integer: 4

  defines class

    Gauge
      reading as Integer?
      doubled as Integer: 0

      default private Gauge()

      Gauge()
        -> start as Integer
        reading: start
        doubled: twice()

      twice()
        <- rtn as Integer: reading * 2

      value()
        <- rtn as Integer: doubled

      default operator ?

    Panel as open
      width as Integer: defaultWidth()
      label as String?

      default private Panel()

      Panel()
        -> title as String
        label: title
        label: decorated()

      private decorated()
        <- rtn as String: `${label} (${width})`

      describe()
        <- rtn as String: label

      default operator ?

  defines program

    ConstructionDemo()
      stdout <- Stdout()
      stdout.println($Gauge(21).value())
      stdout.println(Panel("Main").describe())

Common mistakes

E08061 — twice() reads 'reading', which is not set yet when it is called. Set every property first, then call the object's methods.

Incorrect:

doubled: twice()
        reading: start

Correct:

reading: start
        doubled: twice()

E08061 — Panel is open, so a subtype could override a public decorated() - and the subtype's own properties are not set until after Panel's constructor returns. A private method cannot be overridden, so the constructor may call it.

Incorrect:

decorated()

Correct:

private decorated()

E08061 — A property initialiser runs before the constructor body, when 'reading' is still unset, so it can never call a method of the object. Give it a plain value, or compute it with a function.

Incorrect:

doubled as Integer: twice()

Correct:

doubled as Integer: 0
Other ways to ask this
  • What does E08061 'the object is used before it is fully constructed' mean?
  • Can a property initialiser call a method of its own class?
  • Why can't a constructor in an open class call a method a subclass can override?
  • When can a constructor call its own helper methods in EK9?

Coming from another language?

Java: calling an overridable method from a constructor compiles, and a subclass override that reads its own fields sees null or 0 (Effective Java warns against it; nothing enforces it). Kotlin: 'Leaking this in constructor' and 'accessing non-final property in constructor' are warnings only. C#: the same hazard as Java, a code-analysis warning. Swift: two-phase initialisation - a class cannot call methods on self until every stored property is initialised; EK9 applies the same rule and also forbids overridable calls from the constructor of an open type. EK9: E08061 at compile time.

Keywords: initialiser, overridable, property, two-phase, E08061, helper, NullPointerException, constructor, this, subclass, method, override, field, private, initializer, construction, open