🧬 Inheritance & Polymorphism · Intermediate

Calling overridable methods in constructors in Java

Subclass fields are not yet initialized — a classic trap.

🧩 The mysteryA field is declared String name = "Kai";. You print it and see null. The initializer didn't fail — it simply hadn't run yet.

Recall the order

Creating a Kid: 1. the parent constructor runs; 2. Kid's field initializers run; 3. Kid's constructor body runs. During step 1, Kid's fields still hold their defaults: 0, false, null.

Dispatch works in constructors too

While Base() runs, the object already is a Kid. So if Base() calls an overridable method, Kid's override runs — before Kid's fields are initialized. It sees the defaults.

🔮 Predict it

Say hello too early

What does this print?

class Base {
    Base() { hello(); }
    void hello() { }
}
class Kid extends Base {
    String name = "Kai";
    void hello() {
        System.out.println("Hi " + name); }
}
void main() { new Kid(); }
  1. Hi Kai
  2. Hi null
  3. Nothing
Show the answer

Hi null. Base() dispatches to Kid's hello(), but name = "Kai" only runs after the parent constructor returns.

🔮 Predict it

A list that isn't there yet

What happens?

class Base {
    Base() { init(); }
    void init() { }
}
class Kid extends Base {
    List<String> items = new ArrayList<>();
    void init() { items.add("x"); }
}
void main() { new Kid(); }
  1. Runs fine
  2. Compile error
  3. Throws NullPointerException
Show the answer

It throws **NullPointerException**. Kid.init() runs from Base's constructor while items is still null. The same timing also explains "0 then 5" puzzles with int fields.

Calling from a constructor

✗ Overridable
class Base {
    Base() { init(); }
    void init() { ... }
}

Any subclass can hijack init() before it's ready.

✓ Safe
class Base {
    Base() { init(); }
    private void init() { ... }
}

Only call private, final or static methods from constructors — they can't be overridden.

Java 25's escape hatch

Flexible constructor bodies (JEP 513) let a subclass **assign its own fields before super(...)**. Then even an overridden method called from the parent constructor sees the real value.

class Kid extends Base {
    final String msg;
    Kid() {
        msg = "hi";   // before super!
        super();
    }
    void show() { System.out.println(msg); }
}
💼 In the real world

In real projects

*Effective Java* states it bluntly: constructors must not invoke overridable methods. IntelliJ and Sonar flag "overridable method call in constructor", because these bugs hide until someone writes a subclass — often months later, on another team.

Key takeaways

  1. Overrides run even when called from a parent constructor
  2. Subclass field initializers haven't run yet: fields are 0/null
  3. Call only private, final or static methods from constructors
  4. Java 25: fields assigned before super(...) are already set
🤯 Did you know?

Twist: declare the field as final String name = "Kai"; and it prints Hi Kai. A final field with a constant value is inlined by the compiler, so the uninitialized field is never read.

Practice questions

What does this print?

class Base {
    Base() { show(); }
    void show() { System.out.println("base"); }
}
class Kid extends Base {
    String msg = "hello";
    void show() { System.out.println(msg); }
}
void main() { new Kid(); }
  1. base
  2. hello
  3. null
  4. Throws NullPointerException
Check your answer

null. Base() runs first and dispatches to Kid.show(). Kid's field initializer msg = "hello" hasn't run yet, so msg is still null.

What does this print?

class Base {
    Base() { System.out.println(size()); }
    int size() { return 1; }
}
class Kid extends Base {
    int n = 5;
    int size() { return n; }
}
void main() { System.out.println(new Kid().size()); }
  1. 1 5
  2. 0 5
  3. 5 5
  4. 1 1
Check your answer

0 5. During Base(), Kid.size() runs while n is still 0. Once construction finishes, n is 5.

Next world: Interfaces — how a class can promise to be many things at once, without inheriting anyone's baggage.