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