SHARE
Facebook X Pinterest WhatsApp

Constructors and polymorphism

Bruce Eckel’s Thinking in Java Contents | Prev | Next As usual, constructors are different from other kinds of methods. This is also true when polymorphism is involved. Even though constructors are not polymorphic (although you can have a kind of “virtual constructor,” as you will see in Chapter 11), it’s important to understand the […]

Written By
thumbnail
CodeGuru Staff
CodeGuru Staff
Mar 1, 2001
CodeGuru content and product recommendations are editorially independent. We may make money when you click on links to our partners. Learn More

As


usual,

constructors
are different from other kinds of methods. This is also true when polymorphism
is involved. Even though constructors are not polymorphic (although you can
have a kind of “virtual constructor,” as you will see in Chapter
11), it’s important to understand the way constructors work in complex
hierarchies and with polymorphism. This understanding will help you avoid
unpleasant entanglements.

Order
of constructor calls

The


order of constructor calls was briefly discussed in Chapter 4, but that was


before inheritance and polymorphism were introduced.

A


constructor for the base class is always called in the constructor for a


derived class, chaining upward so that a constructor for every base class is


called. This makes sense because the constructor has a special job: to see that


the object is built properly. A derived class has access to its own members


only, and not to those of the base class (whose members are typically


private

).


Only the base-class constructor has the proper knowledge and access to


initialize its own elements. Therefore, it’s essential that all


constructors get called, otherwise the entire object wouldn’t be


constructed properly. That’s why the compiler enforces a constructor call


for every portion of a derived class. It will silently call the default


constructor if you don’t explicitly call a base-class constructor in the


derived-class constructor body. If there is no default constructor, the


compiler will complain. (In the case where a class has no constructors, the


compiler will automatically synthesize a default constructor.)

Let’s


take a look at an example that shows the effects of composition, inheritance,


and polymorphism on the order of construction:

//: Sandwich.java
// Order of constructor calls
 
class Meal {
  Meal() { System.out.println("Meal()"); }
}
 
class Bread {
  Bread() { System.out.println("Bread()"); }
}
 
class Cheese {
  Cheese() { System.out.println("Cheese()"); }
}
 
class Lettuce {
  Lettuce() { System.out.println("Lettuce()"); }
}
 
class Lunch extends Meal {
  Lunch() { System.out.println("Lunch()");}
}
 
class PortableLunch extends Lunch {
  PortableLunch() {
    System.out.println("PortableLunch()");
  }
}
 
class Sandwich extends PortableLunch {
  Bread b = new Bread();
  Cheese c = new Cheese();
  Lettuce l = new Lettuce();
  Sandwich() {
    System.out.println("Sandwich()");
  }
  public static void main(String[] args) {
    new Sandwich();
  }
} ///:~ 

This


example creates a complex class out of other classes, and each class has a


constructor that announces itself. The important class is


Sandwich

,


which reflects three levels of inheritance (four, if you count the implicit


inheritance from


Object

)


and three member objects. When a


Sandwich

object is created in


main( )

,


the output is:

Meal()
Lunch()
PortableLunch()
Bread()
Cheese()
Lettuce()
Sandwich()

This


means that the order of constructor calls for a complex object is as follows:

  1. The
    base-class
    constructor is called. This step is repeated recursively such that the root of
    the hierarchy is constructed first, followed by the next-derived class, etc.,
    until the most-derived class is reached.
  2. Member
    initializers are called in the order of declaration.
  3. The
    body of the derived-class constructor is called.

The


order of the constructor calls is important. When you inherit, you know all


about the base class and can access any


public

and


protected

members of the base class. This means that you must be able to assume that all


the members of the base class are valid when you’re in the derived class.


In a normal method, construction has already taken place, so all the members of


all parts of the object have been built.


Inside


the constructor, however, you must be able to assume that all members that you


use have been built. The only way to guarantee this is for the base-class


constructor to be called first. Then when you’re in the derived-class


constructor, all the members you can access in the base class have been


initialized. “Knowing that all members are valid” inside the


constructor is also the reason that, whenever possible, you should initialize


all member objects (that is, objects placed in the class using composition) at


their point of definition in the class (e.g.:


b

,


c,

and


l

in the example above). If you follow this practice, you will help ensure that


all base class members


and

member objects of the current object have been initialized. Unfortunately, this


doesn’t handle every case, as you will see in the next section.


Inheritance
and finalize( )

When


you use composition to create a new class, you never worry about finalizing the


member objects of that class. Each member is an independent object and thus is

garbage
collected and finalized regardless of whether it happens to be a member of your
class. With inheritance, however, you must override
finalize( )
in the derived class if you have any special cleanup that must happen as part
of garbage collection. When you override
finalize( )
in an inherited class, it’s important to remember to call the base-class
version of
finalize( ),
since otherwise the base-class finalization will not happen. The following
example proves this:
//: Frog.java
// Testing finalize with inheritance
 
class DoBaseFinalization {
  public static boolean flag = false;
}
 
class Characteristic {
  String s;
  Characteristic(String c) {
    s = c;
    System.out.println(
      "Creating Characteristic " + s);
  }
  protected void finalize() {
    System.out.println(
      "finalizing Characteristic " + s);
  }
}
 
class LivingCreature {
  Characteristic p =
    new Characteristic("is alive");
  LivingCreature() {
    System.out.println("LivingCreature()");
  }
  protected void finalize() {
    System.out.println(
      "LivingCreature finalize");
    // Call base-class version LAST!
    if(DoBaseFinalization.flag)
      try {
        super.finalize();
      } catch(Throwable t) {}
  }
}
 
class Animal extends LivingCreature {
  Characteristic p =
    new Characteristic("has heart");
  Animal() {
    System.out.println("Animal()");
  }
  protected void finalize() {
    System.out.println("Animal finalize");
    if(DoBaseFinalization.flag)
      try {
        super.finalize();
      } catch(Throwable t) {}
  }
}
 
class Amphibian extends Animal {
  Characteristic p =
    new Characteristic("can live in water");
  Amphibian() {
    System.out.println("Amphibian()");
  }
  protected void finalize() {
    System.out.println("Amphibian finalize");
    if(DoBaseFinalization.flag)
      try {
        super.finalize();
      } catch(Throwable t) {}
  }
}
 
public class Frog extends Amphibian {
  Frog() {
    System.out.println("Frog()");
  }
  protected void finalize() {
    System.out.println("Frog finalize");
    if(DoBaseFinalization.flag)
      try {
        super.finalize();
      } catch(Throwable t) {}
  }
  public static void main(String[] args) {
    if(args.length != 0 &&
       args[0].equals("finalize"))
       DoBaseFinalization.flag = true;
    else
      System.out.println("not finalizing bases");
    new Frog(); // Instantly becomes garbage
    System.out.println("bye!");
    // Must do this to guarantee that all 
    // finalizers will be called:
    System.runFinalizersOnExit(true);
  }
} ///:~ 

The


class


DoBaseFinalization

simply holds a flag that indicates to each class in the hierarchy whether to


call

super.finalize( ).
This flag is set based on a command-line argument, so you can view the behavior
with and without base-class finalization.

Each


class in the hierarchy also contains a member object of class


Characteristic

.


You will see that regardless of whether the base class finalizers are called,


the


Characteristic

member objects are always finalized.

Each


overridden


finalize( )

must


have access to at least


protected

members


since the


finalize( )

method


in class


Object

is


protected

and the compiler will not allow you to reduce the access during inheritance.


(“Friendly” is less accessible than


protected

.)


In


Frog.main( )

,


the


DoBaseFinalization

flag


is configured and a single


Frog

object


is created. Remember that garbage collection and in particular finalization


might not happen for any particular object so to enforce this,


System.runFinalizersOnExit(true)

adds the extra overhead to guarantee that finalization takes place. Without


base-class finalization, the output is:

not finalizing bases
Creating Characteristic is alive
LivingCreature()
Creating Characteristic has heart
Animal()
Creating Characteristic can live in water
Amphibian()
Frog()
bye!
Frog finalize
finalizing Characteristic is alive
finalizing Characteristic has heart
finalizing Characteristic can live in water

You


can see that, indeed, no finalizers are called for the base classes of


Frog

.


But if you add the “finalize” argument on the command line, you get:

Creating Characteristic is alive
LivingCreature()
Creating Characteristic has heart
Animal()
Creating Characteristic can live in water
Amphibian()
Frog()
bye!
Frog finalize
Amphibian finalize
Animal finalize
LivingCreature finalize
finalizing Characteristic is alive
finalizing Characteristic has heart
finalizing Characteristic can live in water

Although


the order the member objects are finalized is the same order that they are


created, technically the

order
of finalization of objects is unspecified. With base classes, however, you have
control over the order of finalization. The best order to use is the one
that’s shown here, which is the reverse of the order of initialization.
Following the form that’s used in C++ for destructors, you should perform
the derived-class finalization first, then the base-class finalization.
That’s because the derived-class finalization could call some methods in
the base class that require that the base-class components are still alive, so
you must not destroy them prematurely.

Behavior
of polymorphic methods

inside
constructors

The


hierarchy of constructor calls brings up an interesting dilemma. What happens


if you’re inside a constructor and you call a dynamically-bound method of


the object being constructed? Inside an ordinary method you can imagine what


will happen – the dynamically-bound call is resolved at run-time because


the object cannot know whether it belongs to the class the method is in or some


class derived from it. For consistency, you might think this is what should


happen inside constructors.

This


is not exactly the case. If you call a dynamically-bound method inside a


constructor, the overridden definition for that method is used. However, the


effect

can be rather unexpected, and can conceal some difficult-to-find bugs.

Conceptually,


the constructor’s job is to bring the object into existence (which is


hardly an ordinary feat). Inside any constructor, the entire object might be


only partially formed – you can know only that the base-class objects


have been initialized, but you cannot know which classes are inherited from


you. A dynamically-bound method call, however, reaches “forward” or


“outward” into the inheritance hierarchy. It calls a method in a


derived class. If you do this inside a constructor, you call a method that


might manipulate members that haven’t been initialized yet – a sure


recipe for disaster.

You


can see the problem in the following example:

//: PolyConstructors.java
// Constructors and polymorphism
// don't produce what you might expect.
 
abstract class Glyph {
  abstract void draw();
  Glyph() {
    System.out.println("Glyph() before draw()");
    draw();
    System.out.println("Glyph() after draw()");
  }
}
 
class RoundGlyph extends Glyph {
  int radius = 1;
  RoundGlyph(int r) {
    radius = r;
    System.out.println(
      "RoundGlyph.RoundGlyph(), radius = "
      + radius);
  }
  void draw() {
    System.out.println(
      "RoundGlyph.draw(), radius = " + radius);
  }
}
 
public class PolyConstructors {
  public static void main(String[] args) {
    new RoundGlyph(5);
  }
} ///:~ 

In


Glyph

,


the


draw( )

method is


abstract

,


so it is designed to be overridden. Indeed, you are forced to override it in


RoundGlyph

.


But the


Glyph

constructor calls this method, and the call ends up in


RoundGlyph.draw( )

,


which would seem to be the intent. But look at the output:

Glyph() before draw()
RoundGlyph.draw(), radius = 0
Glyph() after draw()
RoundGlyph.RoundGlyph(), radius = 5

When


Glyph

’s


constructor calls


draw( )

,


the value of


radius

isn’t even the default initial value 1. It’s zero. This would


probably result in either a dot or nothing at all being drawn on the screen,


and you’d be staring, trying to figure out why the program won’t


work.

The


order
of initialization described in the previous section isn’t quite complete,
and that’s the key to solving the mystery. The actual process of
initialization is:
  1. The
    storage allocated for the object is initialized to binary zero before anything
    else happens.
  2. The
    base-class constructors are called as described previously. At this point, the
    overridden
    draw( )
    method is called, (yes,
    before
    the
    RoundGlyph
    constructor
    is called), which discovers a
    radius
    value of zero, due to step 1.
  3. Member
    initializers are called in the order of declaration.
  4. The
    body of the derived-class constructor is called.

There’s


an upside to this, which is that everything is at least initialized to zero (or


whatever zero means for that particular data type) and not just left as


garbage. This includes object handles that are embedded inside a class via


composition. So if you forget to initialize that handle you’ll get an


exception at run time. Everything else gets zero, which is usually a telltale


value when looking at output.

On


the other hand, you should be pretty horrified at the outcome of this program.


You’ve done a perfectly logical thing and yet the behavior is


mysteriously wrong, with no complaints from the compiler. (C++ produces more


rational behavior in this situation.) Bugs like this could easily be buried and


take a long time to discover.

As


a result, a good guideline for constructors is, “Do as little as possible


to set the object into a good state, and if you can possibly avoid it,


don’t call any methods.” The only safe methods to call inside a


constructor are those that are


final

in the base class. (This also applies to


private

methods, which are automatically


final

.)


These cannot be overridden and thus cannot produce this kind of surprise.


Contents

|

Prev

|

Next

Recommended for you...

Top Five Most Popular Front-end Frameworks
Tapas Pal
Mar 5, 2018
DocFlex/Javadoc: Multi-Format Doclet & Rapid Doclet Development Tool
CodeGuru Staff
Apr 23, 2012
Top Down with Memorization Complexity
CodeGuru Staff
Feb 24, 2012
Building and Using the Secret Service Java API
CodeGuru Staff
Feb 21, 2012
CodeGuru Logo

CodeGuru covers topics related to Microsoft-related software development, mobile development, database management, and web application programming. In addition to tutorials and how-tos that teach programmers how to code in Microsoft-related languages and frameworks like C# and .Net, we also publish articles on software development tools, the latest in developer news, and advice for project managers. Cloud services such as Microsoft Azure and database options including SQL Server and MSSQL are also frequently covered.

Property of TechnologyAdvice. © 2025 TechnologyAdvice. All Rights Reserved

Advertiser Disclosure: Some of the products that appear on this site are from companies from which TechnologyAdvice receives compensation. This compensation may impact how and where products appear on this site including, for example, the order in which they appear. TechnologyAdvice does not include all companies or all types of products available in the marketplace.