How do constructors work in multi-level class hierarchies?
← Constructor Delegation · Ref: Q587
In multi-level hierarchies, each level uses super() to initialize its immediate parent. Parameters flow from the most derived class through each parent in the chain.
THREE-LEVEL PATTERN
Grandparent defines base fields and constructor. Parent extends grandparent, adds fields, calls super() to grandparent. Child extends parent, adds fields, calls super() to parent. Each level only passes what its parent needs.
PARAMETER FLOW
Child receives all parameters and passes a subset to parent:
Employee(name, age, role)
super(name, age) //Person gets name and age
Person(name, age)
super(name) //Entity gets name
Entity(name)
this.name: name //stores name
EACH LEVEL INITIALIZES ITS OWN STATE
Each constructor calls super() with the parent's parameters, then initializes its own fields. This keeps each class responsible for its own state.
DEFAULT CONSTRUCTORS IN THE CHAIN
If any level has a default constructor, the child does not need explicit super() for that level.
See Q580 for this() delegation. See Q581 for super() delegation. See Q582 for delegation order. See Q575 for deep override chains.
Example
defines module qa.constructor.hierarchy defines class //Level 1: base entity Entity as open entityName <- String() Entity() -> entityName as String this.entityName: entityName entityName() as pure <- rtn as String: entityName default operator ? //Level 2: adds age Person extends Entity as open personAge <- 0 Person() -> entityName as String personAge as Integer super(entityName) this.personAge: personAge personAge() as pure <- rtn as Integer: personAge default operator ? //Level 3: adds department Employee extends Person department <- String() Employee() -> entityName as String personAge as Integer department as String super(entityName, personAge) this.department: department department() as pure <- rtn as String: department default operator ? defines program HierarchyConstructorsDemo() stdout <- Stdout() emp <- Employee("Alice", 30, "Engineering") stdout.println(`Name: ${emp.entityName()}`) stdout.println(`Age: ${emp.personAge()}`) stdout.println(`Dept: ${emp.department()}`) //Polymorphic use entities <- List() of Entity entities += Entity("Base") entities += Person("Bob", 25) entities += Employee("Charlie", 35, "Sales") for entity in entities stdout.println(`Entity: ${entity.entityName()}`)
Common mistakes
E05050 — super() must be the first statement in the constructor. The parent (Person) must be initialized before the child (Employee) assigns its own fields. See ek9 -h E05050 for details.
Incorrect:
Employee()
->
entityName as String
personAge as Integer
department as String
this.department: department
super(entityName, personAge)
Correct:
Employee()
->
entityName as String
personAge as Integer
department as String
super(entityName, personAge)
this.department: department
E50060 — String has no toUpperCase() method in EK9. Use upperCase() instead. See ek9 -h E50060 for details.
Incorrect:
stdout.println(emp.entityName().toUpperCase())
Correct:
stdout.println(`Name: ${emp.entityName()}`)
Other ways to ask this
- How do I pass parameters through a three-level constructor chain?
- How does super() work with grandparent constructors?
- What is the constructor pattern for deep hierarchies?
Coming from another language?
Java: same pattern, super() in each level. Python: super().__init__() chains through MRO. Kotlin: primary constructor in class header handles delegation. C#: base() at each level. EK9: super() at each level, parameters flow through chain.
Keywords: grandparent, class, constructor, delegation, multi-level, super, parameter, chain, flow, this, hierarchy, constant