Polymorphism is the third essential feature of an object-oriented programming language, after data abstraction and inheritance.
It provides another dimension of separation of interface from implementation, to decouple what from how. Polymorphism allows improved code organization and readability as well as the creation of extensible programs that can be grown not only during the original creation of the project but also when new features are desired. Comment
Encapsulation creates new data types by combining characteristics and behaviors. Implementation hiding separates the interface from the implementation by making the details private. This sort of mechanical organization makes ready sense to someone with a procedural programming background. But polymorphism deals with decoupling in terms of types. In the last chapter, you saw how inheritance allows the treatment of an object as its own type or its base type. This ability is critical because it allows many types (derived from the same base type) to be treated as if they were one type, and a single piece of code to work on all those different types equally. The polymorphic method call allows one type to express its distinction from another, similar type, as long as theyre both derived from the same base type. This distinction is expressed through differences in behavior of the methods that you can call through the base class. Comment
In this chapter, youll learn about polymorphism (also called dynamic binding or late binding or run-time binding) starting from the basics, with simple examples that strip away everything but the polymorphic behavior of the program. Comment
In Chapter 6 you saw how an object can be used as its own type or as an object of its base type. Taking an object reference and treating it as a reference to its base type is called upcasting, because of the way inheritance trees are drawn with the base class at the top. Comment
You also saw a problem arise, which is embodied in the following:
//: c07:music:Music.java // Inheritance & upcasting. package c07.music; import com.bruceeckel.simpletest.*; class Note { private int value; private Note(int val) { value = val; } public static final Note MIDDLE_C = new Note(0), C_SHARP = new Note(1), B_FLAT = new Note(2); } // Etc. class Instrument { public void play(Note n) { System.out.println("Instrument.play()"); } } // Wind objects are instruments // because they have the same interface: class Wind extends Instrument { // Redefine interface method: public void play(Note n) { System.out.println("Wind.play()"); } } public class Music { static Test monitor = new Test(); public static void tune(Instrument i) { // ... i.play(Note.MIDDLE_C); } public static void main(String[] args) { Wind flute = new Wind(); tune(flute); // Upcasting monitor.expect(new String[] { "Wind.play()" }); } } ///:~
The method Music.tune( ) accepts an Instrument reference, but also anything derived from Instrument. In main( ), you can see this happening as a Wind reference is passed to tune( ), with no cast necessary. This is acceptable; the interface in Instrument must exist in Wind, because Wind is inherited from Instrument. Upcasting from Wind to Instrument may narrow that interface, but it cannot make it anything less than the full interface to Instrument. Comment
This program might seem strange to you. Why should anyone intentionally forget the type of an object? This is what happens when you upcast, and it seems like it could be much more straightforward if tune( ) simply takes a Wind reference as its argument. This brings up an essential point: If you did that, youd need to write a new tune( ) for every type of Instrument in your system. Suppose we follow this reasoning and add Stringed and Brass instruments: Comment
//: c07:music2:Music2.java // Overloading instead of upcasting. package c07.music2; import com.bruceeckel.simpletest.*; class Note { private int value; private Note(int val) { value = val; } public static final Note MIDDLE_C = new Note(0), C_SHARP = new Note(1), B_FLAT = new Note(2); } // Etc. class Instrument { public void play(Note n) { System.out.println("Instrument.play()"); } } class Wind extends Instrument { public void play(Note n) { System.out.println("Wind.play()"); } } class Stringed extends Instrument { public void play(Note n) { System.out.println("Stringed.play()"); } } class Brass extends Instrument { public void play(Note n) { System.out.println("Brass.play()"); } } public class Music2 { static Test monitor = new Test(); public static void tune(Wind i) { i.play(Note.MIDDLE_C); } public static void tune(Stringed i) { i.play(Note.MIDDLE_C); } public static void tune(Brass i) { i.play(Note.MIDDLE_C); } public static void main(String[] args) { Wind flute = new Wind(); Stringed violin = new Stringed(); Brass frenchHorn = new Brass(); tune(flute); // No upcasting tune(violin); tune(frenchHorn); monitor.expect(new String[] { "Wind.play()", "Stringed.play()", "Brass.play()" }); } } ///:~
This works, but theres a major drawback: You must write type-specific methods for each new Instrument class you add. This means more programming in the first place, but it also means that if you want to add a new method like tune( ) or a new type of Instrument, youve got a lot of work to do. Add the fact that the compiler wont give you any error messages if you forget to overload one of your methods and the whole process of working with types becomes unmanageable. Comment
Wouldnt it be much nicer if you could just write a single method that takes the base class as its argument, and not any of the specific derived classes? That is, wouldnt it be nice if you could forget that there are derived classes, and write your code to talk only to the base class? Comment
Thats exactly what polymorphism allows you to do. However, most programmers who come from a procedural programming background have a bit of trouble with the way polymorphism works. Comment
The difficulty with Music.java can be seen by running the program. The output is Wind.play( ). This is clearly the desired output, but it doesnt seem to make sense that it would work that way. Look at the tune( ) method:
public static void tune(Instrument i) { // ... i.play(Note.MIDDLE_C); }
It receives an Instrument reference. So how can the compiler possibly know that this Instrument reference points to a Wind in this case and not a Brass or Stringed? The compiler cant. To get a deeper understanding of the issue, its helpful to examine the subject of binding. Comment
Connecting a method call to a method body is called binding. When binding is performed before the program is run (by the compiler and linker, if there is one), its called early binding. You might not have heard the term before because it has never been an option with procedural languages. C compilers have only one kind of method call, and thats early binding. Comment
The confusing part of the above program revolves around early binding because the compiler cannot know the correct method to call when it has only an Instrument reference. Comment
The solution is called late binding, which means that the binding occurs at run-time based on the type of object. Late binding is also called dynamic binding or run-time binding. When a language implements late binding, there must be some mechanism to determine the type of the object at run-time and to call the appropriate method. That is, the compiler still doesnt know the object type, but the method-call mechanism finds out and calls the correct method body. The late-binding mechanism varies from language to language, but you can imagine that some sort of type information must be installed in the objects. Comment
All method binding in Java uses late binding unless a method has been declared final. This means that ordinarily you dont need to make any decisions about whether late binding will occurit happens automatically. Comment
Why would you declare a method final? As noted in the last chapter, it prevents anyone from overriding that method. Perhaps more important, it effectively turns off dynamic binding, or rather it tells the compiler that dynamic binding isnt necessary. This allows the compiler to generate slightly more efficient code for final method calls. However, in most cases it wont make any overall performance difference in your program, so its best to only use final as a design decision, and not as an attempt to improve performance. Comment
Once you know that all method binding in Java happens polymorphically via late binding, you can write your code to talk to the base class and know that all the derived-class cases will work correctly using the same code. Or to put it another way, you send a message to an object and let the object figure out the right thing to do. Comment
The classic example in OOP is the shape example. This is commonly used because it is easy to visualize, but unfortunately it can confuse novice programmers into thinking that OOP is just for graphics programming, which is of course not the case. Comment
The shape example has a base class called Shape and various derived types: Circle, Square, Triangle, etc. The reason the example works so well is that its easy to say a circle is a type of shape and be understood. The inheritance diagram shows the relationships: Comment
The upcast could occur in a statement as simple as:
Shape s = new Circle();
Here, a Circle object is created and the resulting reference is immediately assigned to a Shape, which would seem to be an error (assigning one type to another); and yet its fine because a Circle is a Shape by inheritance. So the compiler agrees with the statement and doesnt issue an error message. Comment
Suppose you call one of the base-class methods (that have been overridden in the derived classes):
s.draw();
Again, you might expect that Shapes draw( ) is called because this is, after all, a Shape referenceso how could the compiler know to do anything else? And yet the proper Circle.draw( ) is called because of late binding (polymorphism). Comment
The following example puts it a slightly different way:
//: c07:Shapes.java // Polymorphism in Java. package c07; import com.bruceeckel.simpletest.*; class Shape { void draw() {} void erase() {} } class Circle extends Shape { void draw() { System.out.println("Circle.draw()"); } void erase() { System.out.println("Circle.erase()"); } } class Square extends Shape { void draw() { System.out.println("Square.draw()"); } void erase() { System.out.println("Square.erase()"); } } class Triangle extends Shape { void draw() { System.out.println("Triangle.draw()"); } void erase() { System.out.println("Triangle.erase()"); } } public class Shapes { static Test monitor = new Test(); public static Shape randShape() { switch((int)(Math.random() * 3)) { default: case 0: return new Circle(); case 1: return new Square(); case 2: return new Triangle(); } } public static void main(String[] args) { Shape[] s = new Shape[9]; // Fill up the array with shapes: for(int i = 0; i < s.length; i++) s[i] = randShape(); // Make polymorphic method calls: for(int i = 0; i < s.length; i++) s[i].draw(); monitor.expect(new Object[] { new TestExpression("%%(Circle|Square|Triangle)" + "\\.draw\\(\\)", s.length) }); } } ///:~
The base class Shape establishes the common interface to anything inherited from Shapethat is, all shapes can be drawn and erased. The derived classes override these definitions to provide unique behavior for each specific type of shape. Comment
The main class Shapes contains a static method randShape( ) that produces a reference to a randomly-selected Shape object each time you call it. Note that the upcasting happens in the return statements, each of which takes a reference to a Circle, Square, or Triangle and sends it out of the method as the return type, Shape. So whenever you call this method you never get a chance to see what specific type it is, since you always get back a plain Shape reference. Comment
main( ) contains an array of Shape references filled through calls to randShape( ). At this point you know you have Shapes, but you dont know anything more specific than that (and neither does the compiler). However, when you step through this array and call draw( ) for each one, the correct type-specific behavior magically occurs, as you can see from one output example: Comment
Circle.draw() Triangle.draw() Circle.draw() Circle.draw() Circle.draw() Square.draw() Triangle.draw() Square.draw() Square.draw()
Of course, since the shapes are all chosen randomly each time, your runs will have different results. The point of choosing the shapes randomly is to drive home the understanding that the compiler can have no special knowledge that allows it to make the correct calls at compile-time. All the calls to draw( ) are made through dynamic binding. Comment
Now lets return to the musical instrument example. Because of polymorphism, you can add as many new types as you want to the system without changing the tune( ) method. In a well-designed OOP program, most or all of your methods will follow the model of tune( ) and communicate only with the base-class interface. Such a program is extensible because you can add new functionality by inheriting new data types from the common base class. The methods that manipulate the base-class interface will not need to be changed at all to accommodate the new classes. Comment
Consider what happens if you take the instrument example and add more methods in the base class and a number of new classes. Heres the diagram:
All these new classes work correctly with the old, unchanged tune( ) method. Even if tune( ) is in a separate file and new methods are added to the interface of Instrument, tune( ) works correctly without recompilation. Here is the implementation of the above diagram: Comment
//: c07:music3:Music3.java // An extensible program. package c07.music3; import com.bruceeckel.simpletest.*; class Instrument { public void play() { System.out.println("Instrument.play()"); } public String what() { return "Instrument"; } public void adjust() {} } class Wind extends Instrument { public void play() { System.out.println("Wind.play()"); } public String what() { return "Wind"; } public void adjust() {} } class Percussion extends Instrument { public void play() { System.out.println("Percussion.play()"); } public String what() { return "Percussion"; } public void adjust() {} } class Stringed extends Instrument { public void play() { System.out.println("Stringed.play()"); } public String what() { return "Stringed"; } public void adjust() {} } class Brass extends Wind { public void play() { System.out.println("Brass.play()"); } public void adjust() { System.out.println("Brass.adjust()"); } } class Woodwind extends Wind { public void play() { System.out.println("Woodwind.play()"); } public String what() { return "Woodwind"; } } public class Music3 { static Test monitor = new Test(); // Doesn't care about type, so new types // added to the system still work right: static void tune(Instrument i) { // ... i.play(); } static void tuneAll(Instrument[] e) { for(int i = 0; i < e.length; i++) tune(e[i]); } public static void main(String[] args) { Instrument[] orchestra = new Instrument[5]; int i = 0; // Upcasting during addition to the array: orchestra[i++] = new Wind(); orchestra[i++] = new Percussion(); orchestra[i++] = new Stringed(); orchestra[i++] = new Brass(); orchestra[i++] = new Woodwind(); tuneAll(orchestra); monitor.expect(new String[] { "Wind.play()", "Percussion.play()", "Stringed.play()", "Brass.play()", "Woodwind.play()" }); } } ///:~
The new methods are what( ), which returns a String reference with a description of the class, and adjust( ), which provides some way to adjust each instrument. Comment
In main( ), when you place something inside the Instrument array you automatically upcast to Instrument. Comment
You can see that the tune( ) method is blissfully ignorant of all the code changes that have happened around it, and yet it works correctly. This is exactly what polymorphism is supposed to provide. Your code changes dont cause damage to parts of the program that should not be affected. Put another way, polymorphism is one of the most important techniques that allow the programmer to separate the things that change from the things that stay the same. Comment
Lets take a different look at the first example in this chapter. In the following program, the interface of the method play( ) is changed in the process of overriding it, which means that you havent overridden the method, but instead overloaded it. The compiler allows you to overload methods so it gives no complaint. But the behavior is probably not what you want. Heres the example:
//: c07:WindError.java // Accidentally changing the interface. import com.bruceeckel.simpletest.*; class NoteX { public static final int MIDDLE_C = 0, C_SHARP = 1, C_FLAT = 2; } class InstrumentX { public void play(int NoteX) { System.out.println("InstrumentX.play()"); } } class WindX extends InstrumentX { // OOPS! Changes the method interface: public void play(NoteX n) { System.out.println("WindX.play(NoteX n)"); } } public class WindError { static Test monitor = new Test(); public static void tune(InstrumentX i) { // ... i.play(NoteX.MIDDLE_C); } public static void main(String[] args) { WindX flute = new WindX(); tune(flute); // Not the desired behavior! monitor.expect(new String[] { "InstrumentX.play()" }); } } ///:~
Theres another confusing aspect thrown in here. In InstrumentX, the play( ) method takes an int that has the identifier NoteX. That is, even though NoteX is a class name, it can also be used as an identifier without complaint. But in WindX, play( ) takes a NoteX reference that has an identifier n. (Although you could even say play(NoteX NoteX) without an error.) Thus it appears that the programmer intended to override play( ) but mistyped the method a bit. The compiler, however, assumed that an overload and not an override was intended. Note that if you follow the standard Java naming convention, the argument identifier would be noteX (lowercase n), which would distinguish it from the class name. Comment
In tune, the InstrumentX i is sent the play( ) message, with one of NoteXs members (MIDDLE_C) as an argument. Since NoteX contains int definitions, this means that the int version of the now-overloaded play( ) method is called, and since that has not been overridden the base-class version is used. Comment
The output is:
InstrumentX.play()
This certainly doesnt appear to be a polymorphic method call. Once you understand whats happening, you can fix the problem fairly easily, but imagine how difficult it might be to find the bug if its buried in a program of significant size. Comment
The distinction between overriding and overloading described in the previous section also appears when you think you are overriding a private method in a derived class. Heres something you might innocently try to do:
//: c07:PrivateOverride.java // Abstract classes and methods. public class PrivateOverride { private void f() { System.out.println("private f()"); } public static void main(String args[]) { PrivateOverride po = new Derived(); po.f(); } } class Derived extends PrivateOverride { public void f() { System.out.println("public f()"); } } ///:~
You might reasonably expect the output to be public f( ), but a private method is automatically final, and is also hidden from the derived class. So Deriveds f( ) in this case is a brand new method its not even overloaded since the base-class version of f( ) isnt visible in Derived.
The result of this is that only non-private methods may be overriden, but you should watch out for the appearance of overriding private methods, which generates no compiler warnings but doesnt do what you might expect. To be clear, you should use a different name from a private base-class method in your derived class.
In all the instrument examples, the methods in the base class Instrument were always dummy methods. If these methods are ever called, youve done something wrong. Thats because the intent of Instrument is to create a common interface for all the classes derived from it. Comment
The only reason to establish this common interface is so it can be expressed differently for each different subtype. It establishes a basic form, so you can say whats in common with all the derived classes. Another way of saying this is to call Instrument an abstract base class (or simply an abstract class). You create an abstract class when you want to manipulate a set of classes through this common interface. All derived-class methods that match the signature of the base-class declaration will be called using the dynamic binding mechanism. (However, as seen in the last section, if the methods name is the same as the base class but the arguments are different, youve got overloading, which probably isnt what you want.) Comment
If you have an abstract class like Instrument, objects of that class almost always have no meaning. That is, Instrument is meant to express only the interface, and not a particular implementation, so creating an Instrument object makes no sense, and youll probably want to prevent the user from doing it. This can be accomplished by making all the methods in Instrument print error messages, but that delays the information until run-time and requires reliable exhaustive testing on the users part. Its always better to catch problems at compile-time. Comment
Java provides a mechanism for doing this called the abstract method[38]. This is a method that is incomplete; it has only a declaration and no method body. Here is the syntax for an abstract method declaration:
abstract void f();
A class containing abstract methods is called an abstract class. If a class contains one or more abstract methods, the class must be qualified as abstract. (Otherwise, the compiler gives you an error message.) Comment
If an abstract class is incomplete, what is the compiler supposed to do when someone tries to make an object of that class? It cannot safely create an object of an abstract class, so you get an error message from the compiler. This way the compiler ensures the purity of the abstract class, and you dont need to worry about misusing it. Comment
If you inherit from an abstract class and you want to make objects of the new type, you must provide method definitions for all the abstract methods in the base class. If you dont (and you may choose not to), then the derived class is also abstract and the compiler will force you to qualify that class with the abstract keyword. Comment
Its possible to create a class as abstract without including any abstract methods. This is useful when youve got a class in which it doesnt make sense to have any abstract methods, and yet you want to prevent any instances of that class. Comment
The Instrument class can easily be turned into an abstract class. Only some of the methods will be abstract, since making a class abstract doesnt force you to make all the methods abstract. Heres what it looks like:
Heres the orchestra example modified to use abstract classes and methods:
//: c07:music4:Music4.java // Abstract classes and methods. package c07.music4; import com.bruceeckel.simpletest.*; import java.util.*; abstract class Instrument { int i; // storage allocated for each public abstract void play(); public String what() { return "Instrument"; } public abstract void adjust(); } class Wind extends Instrument { public void play() { System.out.println("Wind.play()"); } public String what() { return "Wind"; } public void adjust() {} } class Percussion extends Instrument { public void play() { System.out.println("Percussion.play()"); } public String what() { return "Percussion"; } public void adjust() {} } class Stringed extends Instrument { public void play() { System.out.println("Stringed.play()"); } public String what() { return "Stringed"; } public void adjust() {} } class Brass extends Wind { public void play() { System.out.println("Brass.play()"); } public void adjust() { System.out.println("Brass.adjust()"); } } class Woodwind extends Wind { public void play() { System.out.println("Woodwind.play()"); } public String what() { return "Woodwind"; } } public class Music4 { static Test monitor = new Test(); // Doesn't care about type, so new types // added to the system still work right: static void tune(Instrument i) { // ... i.play(); } static void tuneAll(Instrument[] e) { for(int i = 0; i < e.length; i++) tune(e[i]); } public static void main(String[] args) { Instrument[] orchestra = new Instrument[5]; int i = 0; // Upcasting during addition to the array: orchestra[i++] = new Wind(); orchestra[i++] = new Percussion(); orchestra[i++] = new Stringed(); orchestra[i++] = new Brass(); orchestra[i++] = new Woodwind(); tuneAll(orchestra); monitor.expect(new String[] { "Wind.play()", "Percussion.play()", "Stringed.play()", "Brass.play()", "Woodwind.play()" }); } } ///:~
You can see that theres really no change except in the base class. Comment
Its helpful to create abstract classes and methods because they make the abstractness of a class explicit, and tell both the user and the compiler how it was intended to be used. Comment
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 12), its important to understand the way constructors work in complex hierarchies and with polymorphism. This understanding will help you avoid unpleasant entanglements. Comment
The order of constructor calls was briefly discussed in Chapter 4 and again in Chapter 6, but that was before polymorphism was introduced. Comment
A constructor for the base class is always called in the constructor for a derived class, chaining up the inheritance hierarchy 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, its essential that all constructors get called, otherwise the entire object wouldnt be constructed. Thats why the compiler enforces a constructor call for every portion of a derived class. It will silently call the default constructor if you dont 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.) Comment
Lets take a look at an example that shows the effects of composition, inheritance, and polymorphism on the order of construction:
//: c07:Sandwich.java // Order of constructor calls. package c07; import com.bruceeckel.simpletest.*; 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()"); } } public class Sandwich extends PortableLunch { static Test monitor = new Test(); 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(); monitor.expect(new String[] { "Meal()", "Lunch()", "PortableLunch()", "Bread()", "Cheese()", "Lettuce()", "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: Comment
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 youre 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 youre 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 doesnt handle every case, as you will see in the next section. Comment
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, its 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:
//: c07:Frog.java // Testing finalize with inheritance. import com.bruceeckel.simpletest.*; 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() throws Throwable { System.out.println( "LivingCreature finalize"); // Call base-class version LAST! if(DoBaseFinalization.flag) super.finalize(); } } class Animal extends LivingCreature { Characteristic p = new Characteristic("has heart"); Animal() { System.out.println("Animal()"); } protected void finalize() throws Throwable { System.out.println("Animal finalize"); if(DoBaseFinalization.flag) super.finalize(); } } class Amphibian extends Animal { Characteristic p = new Characteristic("can live in water"); Amphibian() { System.out.println("Amphibian()"); } protected void finalize() throws Throwable { System.out.println("Amphibian finalize"); if(DoBaseFinalization.flag) super.finalize(); } } public class Frog extends Amphibian { static Test monitor = new Test(); Frog() { System.out.println("Frog()"); } protected void finalize() throws Throwable { System.out.println("Frog finalize"); if(DoBaseFinalization.flag) super.finalize(); } 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!"); // Force finalizers to be called: System.gc(); monitor.expect(new String[] { "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" }, Test.IGNORE_ORDER + Test.DELAY_SHORT); } } ///:~
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. Comment
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. Comment
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.) Comment
In Frog.main( ), the DoBaseFinalization flag is configured and a single Frog object is created. Remember that garbage collectionand in particular finalizationmight not happen for any particular object, so to enforce this, the call to System.gc( ) triggers garbage collection, and thus finalization. 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 (the member objects are finalized, as you would expect). 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 thats shown here, which is the reverse of the order of initialization. Following the form thats used in C++ for destructors, you should perform the derived-class finalization first, then the base-class finalization. Thats 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. Comment
The hierarchy of constructor calls brings up an interesting dilemma. What happens if youre inside a constructor and you call a dynamically bound method of the object being constructed? Inside an ordinary method you can imagine what will happenthe dynamically bound call is resolved at run-time because the object cannot know whether it belongs to the class that the method is in or some class derived from it. For consistency, you might think this is what should happen inside constructors. Comment
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. Comment
Conceptually, the constructors job is to bring the object into existence (which is hardly an ordinary feat). Inside any constructor, the entire object might be only partially formedyou 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 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 havent been initialized yeta sure recipe for disaster. Comment
You can see the problem in the following example:
//: c07:PolyConstructors.java // Constructors and polymorphism // don't produce what you might expect. import com.bruceeckel.simpletest.*; 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 { static Test monitor = new Test(); public static void main(String[] args) { new RoundGlyph(5); monitor.expect(new String[] { "Glyph() before draw()", "RoundGlyph.draw(), radius = 0", "Glyph() after draw()", "RoundGlyph.RoundGlyph(), radius = 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 Glyphs constructor calls draw( ), the value of radius isnt even the default initial value 1. Its 0. This would probably result in either a dot or nothing at all being drawn on the screen, and youd be left staring, trying to figure out why the program wont work. Comment
The order of initialization described in the previous section isnt quite complete, and thats the key to solving the mystery. The actual process of initialization is:
Theres 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 references that are embedded inside a class via composition, which become null. So if you forget to initialize that reference youll get an exception at run-time. Everything else gets zero, which is usually a telltale value when looking at output. Comment
On the other hand, you should be pretty horrified at the outcome of this program. Youve 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. Comment
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, dont 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. Comment
Once you learn about polymorphism, it can seem that everything ought to be inherited because polymorphism is such a clever tool. This can burden your designs; in fact if you choose inheritance first when youre using an existing class to make a new class, things can become needlessly complicated. Comment
A better approach is to choose composition first, when its not obvious which one you should use. Composition does not force a design into an inheritance hierarchy. But composition is also more flexible since its possible to dynamically choose a type (and thus behavior) when using composition, whereas inheritance requires an exact type to be known at compile-time. The following example illustrates this:
//: c07:Transmogrify.java // Dynamically changing the behavior of // an object via composition. import com.bruceeckel.simpletest.*; abstract class Actor { abstract void act(); } class HappyActor extends Actor { public void act() { System.out.println("HappyActor"); } } class SadActor extends Actor { public void act() { System.out.println("SadActor"); } } class Stage { Actor a = new HappyActor(); void change() { a = new SadActor(); } void go() { a.act(); } } public class Transmogrify { static Test monitor = new Test(); public static void main(String[] args) { Stage s = new Stage(); s.go(); // Prints "HappyActor" s.change(); s.go(); // Prints "SadActor" monitor.expect(new String[] { "HappyActor", "SadActor" }); } } ///:~
A Stage object contains a reference to an Actor, which is initialized to a HappyActor object. This means go( ) produces a particular behavior. But since a reference can be rebound to a different object at run-time, a reference for a SadActor object can be substituted in a and then the behavior produced by go( ) changes. Thus you gain dynamic flexibility at run-time. (This is also called the State Pattern. See Thinking in Patterns with Java, downloadable at www.BruceEckel.com.) In contrast, you cant decide to inherit differently at run-time; that must be completely determined at compile-time. Comment
A general guideline is Use inheritance to express differences in behavior, and fields to express variations in state. In the above example, both are used: two different classes are inherited to express the difference in the act( ) method, and Stage uses composition to allow its state to be changed. In this case, that change in state happens to produce a change in behavior. Comment
When studying inheritance, it would seem that the cleanest way to create an inheritance hierarchy is to take the pure approach. That is, only methods that have been established in the base class or interface are to be overridden in the derived class, as seen in this diagram:
This can be termed a pure is-a relationship because the interface of a class establishes what it is. Inheritance guarantees that any derived class will have the interface of the base class and nothing less. If you follow the above diagram, derived classes will also have no more than the base class interface. Comment
This can be thought of as pure substitution, because derived class objects can be perfectly substituted for the base class, and you never need to know any extra information about the subclasses when youre using them:
That is, the base class can receive any message you can send to the derived class because the two have exactly the same interface. All you need to do is upcast from the derived class and never look back to see what exact type of object youre dealing with. Everything is handled through polymorphism. Comment
When you see it this way, it seems like a pure is-a relationship is the only sensible way to do things, and any other design indicates muddled thinking and is by definition broken. This too is a trap. As soon as you start thinking this way, youll turn around and discover that extending the interface (which, unfortunately, the keyword extends seems to encourage) is the perfect solution to a particular problem. This could be termed an is-like-a relationship because the derived class is like the base classit has the same fundamental interfacebut it has other features that require additional methods to implement:
While this is also a useful and sensible approach (depending on the situation) it has a drawback. The extended part of the interface in the derived class is not available from the base class, so once you upcast you cant call the new methods:
If youre not upcasting in this case, it wont bother you, but often youll get into a situation in which you need to rediscover the exact type of the object so you can access the extended methods of that type. The following section shows how this is done. Comment
Since you lose the specific type information via an upcast (moving up the inheritance hierarchy), it makes sense that to retrieve the type informationthat is, to move back down the inheritance hierarchyyou use a downcast. However, you know an upcast is always safe; the base class cannot have a bigger interface than the derived class, therefore every message you send through the base class interface is guaranteed to be accepted. But with a downcast, you dont really know that a shape (for example) is actually a circle. It could instead be a triangle or square or some other type. Comment
To solve this problem there must be some way to guarantee that a downcast is correct, so you wont accidentally cast to the wrong type and then send a message that the object cant accept. This would be quite unsafe. Comment
In some languages (like C++) you must perform a special operation in order to get a type-safe downcast, but in Java every cast is checked! So even though it looks like youre just performing an ordinary parenthesized cast, at run-time this cast is checked to ensure that it is in fact the type you think it is. If it isnt, you get a ClassCastException. This act of checking types at run-time is called run-time type identification (RTTI). The following example demonstrates the behavior of RTTI:
//: c07:RTTI.java // Downcasting & Run-time Type Identification (RTTI). // {ThrowsException} import java.util.*; class Useful { public void f() {} public void g() {} } class MoreUseful extends Useful { public void f() {} public void g() {} public void u() {} public void v() {} public void w() {} } public class RTTI { public static void main(String[] args) { Useful[] x = { new Useful(), new MoreUseful() }; x[0].f(); x[1].g(); // Compile-time: method not found in Useful: //! x[1].u(); ((MoreUseful)x[1]).u(); // Downcast/RTTI ((MoreUseful)x[0]).u(); // Exception thrown } } ///:~
As in the diagram, MoreUseful extends the interface of Useful. But since its inherited, it can also be upcast to a Useful. You can see this happening in the initialization of the array x in main( ). Since both objects in the array are of class Useful, you can send the f( ) and g( ) methods to both, and if you try to call u( ) (which exists only in MoreUseful) youll get a compile-time error message. Comment
If you want to access the extended interface of a MoreUseful object, you can try to downcast. If its the correct type, it will be successful. Otherwise, youll get a ClassCastException. You dont need to write any special code for this exception, since it indicates a programmer error that could happen anywhere in a program. Comment
Theres more to RTTI than a simple cast. For example, theres a way to see what type youre dealing with before you try to downcast it. All of Chapter 12 is devoted to the study of different aspects of Java run-time type identification. Comment
Polymorphism means different forms. In object-oriented programming, you have the same face (the common interface in the base class) and different forms using that face: the different versions of the dynamically bound methods. Comment
Youve seen in this chapter that its impossible to understand, or even create, an example of polymorphism without using data abstraction and inheritance. Polymorphism is a feature that cannot be viewed in isolation (like a switch statement can, for example), but instead works only in concert, as part of a big picture of class relationships. People are often confused by other, non-object-oriented features of Java, like method overloading, which are sometimes presented as object-oriented. Dont be fooled: If it isnt late binding, it isnt polymorphism. Comment
To use polymorphismand thus object-oriented techniqueseffectively in your programs you must expand your view of programming to include not just members and messages of an individual class, but also the commonality among classes and their relationships with each other. Although this requires significant effort, its a worthy struggle, because the results are faster program development, better code organization, extensible programs, and easier code maintenance. Comment
Solutions to selected exercises can be found in the electronic document The Thinking in Java Annotated Solution Guide, available for a small fee from www.BruceEckel.com.
[38] For C++ programmers, this is the analogue of C++s pure virtual function.