In what order do property initialisers run, and which properties can an initialiser read?

← Classes and OOP · Ref: Q1397

Property initialisers run in declaration order, before the constructor body. So an initialiser may read a property of its own type only if that property is declared EARLIER and has its own initialiser:

  stock as Integer: startingStock()
  reserved as Integer: 2
  available as Integer: stock - reserved

WHAT IT CANNOT READ

- A property declared later: its initialiser has not run yet.
- Itself: 'reserved as Integer: reserved + 2' reads a value that does not exist yet.
- A property declared with '?' and set in the constructor: the constructor body has not run yet, even though it is declared earlier.
All three are the same mistake - a property read before it has a value - and all report E08060 (not initialised before use). Read a property by its plain name: an initialiser may not use 'this' at all, so 'this.later' (or even 'this.stock') is E08061 - see Q2180. (A LOCAL variable whose initialiser reads itself is E08010, used before definition.)

WHAT IT CAN USE

- Earlier properties that have initialisers.
- Functions ('startingStock()') and literals.
- Not the object's own methods: the object is not complete while initialisers run (E08061, see Q1396).

A method can read every property: by the time a method runs, the object has been fully constructed. Before these checks, each of the shapes above compiled clean and failed with a NullPointerException when the object was built.

See Q104 for uninitialised properties. See Q1396 for using the object during construction.

Example

defines module qa.oop.initialiser.order

  defines function

    startingStock()
      <- rtn as Integer: 12

  defines class

    Inventory
      location as String?
      stock as Integer: startingStock()
      reserved as Integer: 2
      available as Integer: stock - reserved

      default private Inventory()

      Inventory()
        -> place as String
        location: place

      summary()
        <- rtn as String: `${available} available at ${location}`

      default operator ?

  defines program

    InitialiserOrderDemo()
      stdout <- Stdout()
      stdout.println(Inventory("Leeds").summary())

Common mistakes

E08060 — Initialisers run in declaration order, so 'reserved' has no value yet when 'available' is computed. Declare a property before any initialiser that reads it.

Incorrect:

available as Integer: stock - reserved
      reserved as Integer: 2

Correct:

reserved as Integer: 2
      available as Integer: stock - reserved

E08060 — 'location' is declared earlier but without a value - the constructor sets it, and the constructor body runs after every initialiser. Read it in a method, or compute this value in the constructor.

Incorrect:

available as Integer: length location

Correct:

available as Integer: stock - reserved

E08060 — An initialiser cannot read the property it is initialising: it has no value yet. Use a literal, an earlier property or a function.

Incorrect:

reserved as Integer: reserved + 2

Correct:

reserved as Integer: 2
Other ways to ask this
  • Why is 'not initialised before use' reported for a property initialiser?
  • Can a property initialiser read another property of the same class?
  • Why can't a property initialiser read a property the constructor sets?
  • Why does E08060 appear on a property initialiser?

Coming from another language?

Java: a field initialiser that reads a later field by simple name is an 'illegal forward reference' compile error, but 'this.later' compiles and reads the default null/0; reading a field the constructor sets also sees null/0. Kotlin: property initialisers run in order and a forward read sees null for reference types (a warning at best). C#: field initialisers cannot reference instance members at all. EK9: order is checked at compile time - a later property, the property itself, or one the constructor sets is E08060, and 'this' in an initialiser is E08061, so the only spelling is the plain name.

Keywords: E08010, property, initializer, order, forward, field, initialiser, declaration, E08060, NullPointerException, reference, constructor, self