How do field initialization and constructor delegation interact?

← Constructor Delegation · Ref: Q585

Fields in EK9 must be initialized inline at declaration. Constructors then optionally reassign fields using ':' or ':=?'. Understanding the interaction between field defaults and constructor assignment is essential.

FIELD DEFAULTS

Every field must have an inline default:

  name <- String()    //initialized to unset String
  count <- 0          //initialized to 0

Fields cannot be left uninitialized (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 — Using a field before it has been declared is a forward reference error. Fields must be declared before being referenced. 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: fields must be initialized inline, E08180 if not, constructors reassign with ':'.

Keywords: order, default, delegation, E08180, field, super, this, initialization, constructor, constant, inline