How do field initialization and constructor delegation interact?
← Constructor Delegation · Ref: Q585
A field in EK9 is either initialized inline at declaration, or declared 'as T?' (no value) and then set by every public constructor. Constructors can also reassign inline-initialised fields using ':' or ':=?'. Understanding the interaction between field defaults and constructor assignment is essential.
FIELD DEFAULTS
Give a field an inline default:
name <- String() //initialized to unset String count <- 0 //initialized to 0
The alternative is 'name as String?' with no value, which needs the default constructor made private and every public constructor to set it (see Q886). A plain 'name as String' with no '?', no initialiser and no injection '!' is E08180.
CONSTRUCTOR ASSIGNMENT
Constructors use ':' to reassign fields after defaults are applied:
MyClass()
-> name as String
this.name: name //reassigns from default to parameter
ORDER OF INITIALIZATION
Field defaults are applied when the object is created. Then the constructor body runs. If delegation is used, the delegated constructor runs first.
COMBINING WITH DELEGATION
When using this() or super(), field defaults apply first, then the delegated constructor runs, then remaining statements in the calling constructor.
See Q580 for this() delegation. See Q581 for super() delegation. See Q104 for uninitialized properties. See Q563 for pure constructor field assignment.
Example
defines module qa.constructor.fieldinit defines class //Fields with defaults, constructors reassign Configuration hostname <- "localhost" portNumber <- 8080 maxRetries <- 3 //One-arg: only changes hostname Configuration() -> hostname as String this.hostname: hostname //Two-arg: changes hostname and port Configuration() -> hostname as String portNumber as Integer this.hostname: hostname this.portNumber: portNumber //Three-arg: changes all fields Configuration() -> hostname as String portNumber as Integer maxRetries as Integer this.hostname: hostname this.portNumber: portNumber this.maxRetries: maxRetries hostname() as pure <- rtn as String: hostname portNumber() as pure <- rtn as Integer: portNumber maxRetries() as pure <- rtn as Integer: maxRetries default operator ? defines program FieldInitDelegationDemo() stdout <- Stdout() //One-arg: port and retries keep defaults cfg1 <- Configuration("api.example.com") stdout.println(`${cfg1.hostname()}: ${cfg1.portNumber()}, retries ${cfg1.maxRetries()}`) //Two-arg: retries keeps default cfg2 <- Configuration("db.example.com", 5432) stdout.println(`${cfg2.hostname()}: ${cfg2.portNumber()}, retries ${cfg2.maxRetries()}`) //Three-arg: all custom cfg3 <- Configuration("cache.example.com", 6379, 5) stdout.println(`${cfg3.hostname()}: ${cfg3.portNumber()}, retries ${cfg3.maxRetries()}`)
Common mistakes
E04050 — A property initialiser cannot be another property or a complex expression - this is E04050 even when the referenced property is declared first. Initialise with a literal or a constructor call, and derive values from other properties inside a constructor. See ek9 -h E04050 for details.
Incorrect:
hostname <- portNumber portNumber <- 8080
Correct:
hostname <- "localhost" portNumber <- 8080 maxRetries <- 3
Other ways to ask this
- When should I use field defaults vs constructor assignment?
- How do inline field values work with constructors?
- What is the field initialization order in EK9?
Coming from another language?
Java: fields can be uninitialized (get default zero/null values). Python: fields set in __init__. Kotlin: properties initialized in init block or primary constructor. EK9: a field is initialized inline or declared 'as T?' and set by every public constructor; E08180 for a plain 'as T' with neither; constructors reassign with ':'.
Keywords: super, field, E08180, delegation, default, order, inline, constant, constructor, initialization, this