How do override and access modifiers interact in a three-level class hierarchy?
← Override Mechanics · Ref: Q676
When overriding in multi-level class hierarchies, three rules must hold at every level: the override keyword must be present, the access modifier must be exactly the parent's, and the parameter types must match exactly. These rules apply equally to METHODS and OPERATORS.
OVERRIDE ON METHODS AND OPERATORS
The override keyword works identically on both:
override describe() as pure method override override operator ? as pure operator override override operator $ as pure operator override
If the parent defines it, the child must say override.
OVERRIDE KEYWORD REQUIRED (E05120)
Any method or operator that replaces a parent version MUST use 'override'. Without it, the compiler reports E05120. This applies to operator ? (inherited from base type), operator $ (if parent defines it), and all other operators.
ACCESS MODIFIER MATCHING (E05130)
An override keeps the parent's access modifier exactly. Narrowing (public to protected) and widening (protected to public) both trigger E05130 ('methods with same signature have different access modifiers'). 'override private' is E07010 - a private method is never an override.
SIGNATURE MATCHING (E05110, E07440)
Parameter types must match exactly - different parameter types make a new overload, and 'override' on it is E05110. The return type may be narrowed to a subtype (a covariant return); an incompatible return type is E07440.
THREE-LEVEL CHAIN
Grandparent declares the contract. Parent overrides it. Grandchild overrides it again. Each level must independently satisfy the override, access, and signature rules — for both methods and operators.
See Q570 for override basics. See Q575 for deep override chains. See Q579 for trait override chains.
Example
defines module qa.override.accesshierarchy defines class <?- Level 1: Grandparent declares protected methods. These form the contract for all descendants. -?> Grandparent as abstract protected describe() <- rtn as String: "grandparent" protected classify() as pure abstract <- rtn as String? default operator ? <?- Level 2: Parent overrides with matching access. Protected stays protected, override keyword present. -?> Parent extends Grandparent as open override protected describe() <- rtn as String: "parent" override protected classify() as pure <- rtn as String: "parent-class" default operator ? <?- Level 3: Child overrides again, maintaining contract. Each level independently satisfies all three rules. -?> Child extends Parent override protected describe() <- rtn as String: "child" override protected classify() as pure <- rtn as String: "child-class" default operator ? defines program OverrideAccessHierarchyDemo() stdout <- Stdout() child <- Child() stdout.println(`Override chain verified: ${child?}`)
Common mistakes
E05120 — Parent's describe() replaces Grandparent's, so it must say 'override'; without it the compiler reports E05120. (In a class that is NOT open, as Child is, the same omission is reported first as E07260 - a 'protected' method is only allowed in an open class.) See ek9 -h E05120 for details.
Incorrect:
protected describe() <- rtn as String: "parent"
Correct:
override protected describe() <- rtn as String: "parent"
E07010 — A private method is never an override, so 'override private' is rejected with E07010. The override keeps the parent's access modifier exactly - protected here (any other modifier is E05130). See ek9 -h E07010 for details.
Incorrect:
override private classify() as pure <- rtn as String: "child-class"
Correct:
override protected classify() as pure <- rtn as String: "child-class"
E05110 — The override signature must match the parent exactly. Adding, removing, or changing parameter types means the method does not actually override the parent and triggers E05110. See ek9 -h E05110 for details.
Incorrect:
override protected describe() -> extra as Integer <- rtn as String: "child"
Correct:
override protected describe() <- rtn as String: "child"
Other ways to ask this
- What is E05120 override required in a class hierarchy?
- What is E05130 access modifier mismatch in override?
- What is E05110 override takes other parameter types than the parent?
- How does override protected work in deep class hierarchies?
Coming from another language?
Java: @Override annotation, access can widen (protected to public), signatures must match. Kotlin: 'override' keyword mandatory, access widening allowed. Rust: no inheritance, trait impl must match. Python: no override keyword, duck typing. EK9: 'override' keyword mandatory, access modifier must match exactly (no widening, unlike Java), parameter types must match exactly, covariant returns allowed.
Keywords: chain, hierarchy, class, protected, override, virtual, access, E05110, inherit, E05120, abstract, E05130, open, signature