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