Memra

Polymorphism pitfalls & constructors

◈ 5 cards

Constructor call order under inheritance, the calling-an-overridable-method-in-a-constructor trap, and what is NOT polymorphic.

The order of construction under inheritance

When you create a subclass object, Java builds it base-first. The exact order is:

  1. The superclass constructor runs (recursively, up to Object first).
  2. The subclass's instance field initializers run, in source order.
  3. The rest of the subclass constructor body runs.

This base-first rule sets up the famous trap.

Worked example: a constructor that calls an overridable method

class Glyph {
    Glyph() { draw(); }                  // calls an overridable method!
    void draw() { System.out.println("Glyph.draw"); }
}

class RoundGlyph extends Glyph {
    private int radius = 5;              // field initializer
    @Override void draw() {
        System.out.println("RoundGlyph.draw, radius = " + radius);
    }
}

Now new RoundGlyph(); prints:

RoundGlyph.draw, radius = 0

Why 0, not 5? Trace it:

  1. RoundGlyph's construction begins; the implicit super() runs Glyph() first.
  2. Inside Glyph(), draw() is called. Because draw() is overridden and dispatch is dynamic, it runs RoundGlyph.draw() — the subclass version — even though we are still inside the superclass constructor.
  3. But RoundGlyph's field initializer (radius = 5) has not run yet — it runs after super() returns. So radius still holds its default value, 0.

The overridden method runs against a half-built object. The lesson: never call an overridable (non-final, non-private, non-static) method from a constructor. Either make such helper methods final/private, or don't call them during construction.

What is NOT polymorphic

Late binding applies only to overridable instance methods. Three things resolve by the static (declared) type instead, and the exam loves to test this:

  • Fields are not polymorphic. A field reference is resolved by the declared type. If both Super and Sub declare a field x, then Super ref = new Sub(); ref.x reads Super's x (the field is hidden, not overridden).
  • static methods are not polymorphic. They are hidden, not overridden, and bound to the declared type.
  • private methods are not overridable at all. A subclass method with the same name is a new, independent method; the superclass's own code keeps calling the superclass's private version.
new RoundGlyph()implicit super() runs firstGlyph() calls draw()dispatch is dynamicRoundGlyph.draw()reads radius = 0radius = 5field initializer, too lateRoundGlyph bodythe rest of the constructor
Read stage 3 against stage 4. Construction is base-first, so the override executes while RoundGlyph is still half-built and radius still holds its default 0 — the assignment of 5 comes one stage too late.
member kindbound bySuper ref = new Sub()instance methodruntime typeSub's override runsfield xdeclared typeSuper's x — hiddenstatic methoddeclared typeSuper's — hiddenprivate methoddeclared typenot overridable at allHiding is not overriding.
Only one row is polymorphic. For every other member kind the compiler has already decided from the declared type, so a Super-typed reference keeps reading Super members however the object was made.
NORMAL ~/memra/learn/comp-308/polymorphism-pitfalls-and-constructors utf-8 LF