Matthew W
Matthew W

Reputation: 307

Why are member objects initialized after the super class's constructor?

I ran into an interesting problem yesterday and while the fix was quite simple, I'm still a bit fuzzy on the "why" of it.

I have a class that has a private member variable that is assigned when it is instantiated, however if it is used in an abstract function that is called by the super class's constructor, the variable does not have a value. The solution to the problem was quite simple, I simply had to declare the variable as static and it was assigned correctly. Some code to illustrate the problem:

class Foo extends BaseClass
{
    private final String bar = "fooBar!";
    public Foo()
    {
        super();
    }

    @Override 
    public void initialize()
    {
        System.out.println(bar);
    }
}

And the base class:

abstract class BaseClass
{
    public BaseClass()
    {
        initialize();
    }

    public abstract void initialize();
}

In this example, when we call new Foo(); it will output (null) instead of the expected fooBar!

Since we're instantiated an object of type Foo, should its members not be allocated and assigned prior to calling its (and consequently its super class's) constructor? Is this specified somewhere in the Java language or is it JVM specific?

Thanks for any insight!

Upvotes: 8

Views: 2949

Answers (3)

bvdb
bvdb

Reputation: 24770

There's just one thing I would like to add to the accepted answer, because I don't entirely agree with his conclusion.

We've all done this.

class Engine {
  public Engine() {
    init();
  }

  void init() {
    lockDoors();
    releasePressure();
    tightenSeatbelts();
    launchRocket();
  }

  ...
}

Now the question is, which access modifier should we add to our init() function. Should it be private or protected.

  • make it private <-- keeps subclasses out
  • make it protected <-- allows subclasses in

Before you make a choice

Now first of all, you should realize that (almost) all code in the Engine class can be replaced by a subclass.

  • code in a public function, can easily be overridden
  • code in a protected function, can easily be overridden
  • code in a private function, can be replaced by overriding all methods that call it.

Well, there is just one exception:

  • you can never modify the code of a constructor
  • you can never avoid a private method being called from the constructor of a super class.
  • (and of course, you cannot replace a final method)

Protected init() - the wrong way

Let's say the init() method is protected there is indeed a pitfall. It is tempting to override it to add features as follows. That would indeed be a mistake.

class SubEngine extends Engine {
  int screws = 5;

  void init() {
    tightenScrews();
    super.init();
  }

  void tightenScrews() {
    // this won't print 5, but it will print 0.
    System.out.println("tightening " + screws + " screws"); 
  }
}

Protected init() - the right way

So, basically, you should just disable the parents code and postpone execution to your own constructor instead.

class SubEngine extends Engine {
  int screws = 5;

  public SubEngine() {
    initSubEngine();
  }

  void init() {
    // disable parent code
  }

  void initSubEngine() {
    tightenScrews();
    super.init();
  }

  void tightenScrews() {
    // this will print 5 as expected
    System.out.println("tightening " + screws + " screws"); 
  }
}

Private init() - you may need a phonecall

Now, what if the init() method is private ?

  • Like mentioned above, there is no way to disable the code of a parent constructor. And if init() is private you simply cannot disable it.
  • You'll end up copying the entire Engine class, perhaps just to add 1 line of code.
  • And that may not be the end of it. Even after copying your class, your copied object won't be an Engine meaning that you won't be able to use your EngineUtil#inspectEngine(Engine engine) function.
  • Perhaps somebody knew this in advance and made an IEngine interface. Then you can get away with it.
  • In practice it means you'll have to take your phone, and call to that other department that made the Engine class, and ask them to change their code a little to take away some restrictions.

Intelligent design

There is another way. Constructors are for setting variables. They shouldn't activate anything. Everytime you see a class creating a new Thread from their constructor (or through a private method) that should be a red flag.

class Engine {
  public Engine() {
  }

  public void init() {
    lockDoors();
    releasePressure();
    tightenSeatbelts();
    launchRocket();
  }

  // and you probably also want one of these
  public void shutdown() { ... }

  ...
}

Intention

Of course, your intention may very well be not to open up your code. Perhaps you really don't want to allow others to extend your classes. There certainly can be cases where you want to lock people out.

Be aware that it will also make it harder to write tests for your code.

Anyway that's a different scenario.

Upvotes: 0

Johan Sj&#246;berg
Johan Sj&#246;berg

Reputation: 49237

The assignment of bar = "fooBar!"; is inlined into the constructor during compile time.

The superclass constructor runs before the subclass constructor, hence it would only be natural that the statement is executed afterwards.

Generally though, it's bad practice to call overridable methods from a constructor.

Upvotes: 8

Miserable Variable
Miserable Variable

Reputation: 28761

It is as defined by the Java Language Specification. Changing it to static will almost never be and acceptable solution in real world situation.

See JLS 4.12.5 Initial Values of Variablesand JLS 8.3.2 Initialization of Fields

Overall, it is bad practice to call a non-final method from a constructor. the reason being that it could (and if the method is abstract then definitely does) call method in the class that has not yet been initialized: When new Foo() is executed, the BaseClass initializer (constructor) gets called before the Foo constructor, so Foo.initialize is essentially working on an Object that has not been fully constructed yet.

Upvotes: 1

Related Questions