Constructors and polymorphism

Bruce Eckel’s Thinking in Java Contents | Prev | Next

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( )

//: 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.)

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

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.

  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.

More by Author

Must Read