Topper Harley
Topper Harley

Reputation: 12384

Why is my subclass constructor not called?

I have this inheritance structure:

public abstract class Mom {
    int dummy;
    Mom() {
        dummy = 0;
    }
    Mom(int d) {
        this();
        dummy = d;
    }
}
public class Kid extends Mom {
    String foo;
    Kid() {
        super();
        foo = "";
    }
    Kid(int d) {
        super(d);
    }
}
// ...
Kid kiddo = new Kid(10);
// kiddo.foo == null !

My argument-less constructor of Kid is never called! Here's what I expected:

  1. new Kid(10)Kid#Kid(int)
  2. super(d)Mom#Mom(int)
  3. this()Kid#Kid() // doh!!
  4. super()Mom#Mom()

Is it possible, from Mom, to call Kid's argument-less constructor?
I guess it's not, and I'll add an abstract method[1] init() that Mom will call and that Kids will have to override.
But I just wanted to know the exact reason, and if possible, examples proving why wanting to call subclass' constructor is a bad idea (even if the subclass' constructor does call super()).

// in Mom:
protected abstract void init();
public Mom() {
    dummy = 0;
    init();
}

// in Kid:
@Override
protected abstract void init() {
    foo = "";
}

Upvotes: 0

Views: 1085

Answers (3)

nd.
nd.

Reputation: 8942

From the JLS §8.8.7.1 (emphasis by me):

  • Alternate constructor invocations begin with the keyword this (possibly prefaced with explicit type arguments). They are used to invoke an alternate constructor of the same class.

  • Superclass constructor invocations begin with either the keyword super (possibly prefaced with explicit type arguments) or a Primary expression. They are used to invoke a constructor of the direct superclass.

So, a this-constructor-invocation always refers to the same class, never to a child class.

While it is possible to invoke virtual methods in a constructor, it is unsafe and considered bad practice as it may result in those methods working with partly initialized object instances.

For your problem, there are several possible solutions:

  1. Initialize the member foo at declaration, i.e. foo = "";. This is also known as field initializers
  2. Use an instance initializer: { foo = ""; }. Note that you can have more than one instance initializer if needed in your class.
  3. Bite the bullet and repeat the initialization in all constructors

According to JLS §12.5, initialization in (1) and (2) is always executed before constructors themselves are called, so you have a well-defined object initialization without the need of resorting to problematic patterns.

If a member is initialized multiple times, then the last initialization wins:

4) Execute the instance initializers and instance variable initializers for this class, assigning the values of instance variable initializers to the corresponding instance variables, in the left-to-right order in which they appear textually in the source code for the class.

If the same field is initialized in a constructor as well, then the constructor wins.

Upvotes: 2

Peter Lawrey
Peter Lawrey

Reputation: 533870

The way I would arrange these so you don't need to call every constructor.

public abstract class Parent {
    final int dummy;

    Parent () {
        this(0);
    }
    Parent (int d) {
        dummy = d;
    }
}

public class Kid extends Parent {
    final String foo = "";

    Kid() {
    }

    Kid(int d) {
        super(d);
    }
}

Using final ensures that every fields is set once.


Its considered bad practice to call any override-able method from a constructor, so making constructors override-able which be a bad idea.


this() calls the constructor of the same class because constructors don't follow inheritance (nor do static methods)

new Kid(10) --> Kid#Kid(int)
super(d) --> Mom#Mom(int)
this() --> Mom#Mom()

Constructors do this otherwise you are in danger for calling the same constructor more than once and there is no way to guarantee that final methods are set only once.

Upvotes: 6

jolivier
jolivier

Reputation: 7655

You should have these constructors for the Kid class:

Kid(int i) {
  super(i);
  whatever();
}

Kid () {
  this( DEFAULT_VALUE);
}

so that all call to parent constructor are made via fully qualified constructor of child class. And have a default behavior for all constructor of your class that is not bypassed as it is the case with your current code.

Upvotes: 0

Related Questions