Published: March 13, 2002
By Richard G. Baldwin
Java Programming Notes # 1614
The first lesson in the group was entitled The Essence of OOP Using Java, Objects, and Encapsulation. That lesson, and each of the lessons following that one, has provided explanations of certain aspects of the essence of Object-Oriented Programming using Java. The previous lesson was entitled The Essence of OOP using Java, Runtime Polymorphism through Inheritance.
Necessary and significant aspects
This miniseries will describe and discuss the necessary and significant aspects of OOP using Java.
If you have a general understanding of computer programming, you should be able to read and understand the lessons in this miniseries, even if you don't have a strong background in the Java programming language.
Viewing tip
You may find it useful to open another copy of this lesson in a separate browser window. That will make it easier for you to scroll back and forth among the different listings while you are reading about them.
Supplementary material
I recommend that you also study the other lessons in my extensive collection of online Java tutorials. You will find those lessons published at Gamelan.com. However, as of the date of this writing, Gamelan doesn't maintain a consolidated index of my Java tutorial lessons, and sometimes they are difficult to locate there. You will find a consolidated index at Baldwin's Java Programming Tutorials.
The meaning of the word polymorphism is something like one name, many forms.
How does Java implement polymorphism?
Polymorphism manifests itself in Java in the form of multiple methods having the same name.
In some cases, multiple methods have the same name, but different formal argument lists (overloaded methods, which were discussed in a previous lesson).
In other cases, multiple methods have the same name, same return type, and same formal argument list (overridden methods).
Three distinct forms of polymorphism
From a practical programming viewpoint, polymorphism manifests itself in three distinct forms in Java:
I discussed runtime polymorphism implemented through method overriding and class inheritance in a previous lesson. However, before leaving that topic, I need to discuss an important special case.
In this lesson, I will discuss the use of the Object class as a completely generic type for storing references to objects of subclass types, and will explain how that results in a very useful form of runtime polymorphism.
I will briefly discuss the default versions of eleven methods defined in the Object class, and will explain that in many cases, those default versions are meant to be overridden.
I will cover method overriding through the Java interface in a subsequent lesson.
Java supports a framework, known as the Java Collections Framework, which you can read about in other tutorial lessons on my web site.
Without getting into a lot of detail, the framework provides several concrete implementations of interfaces with names like list, set, and map.
The classes that provide the implementations have names like LinkedList, TreeSet, ArrayList, Vector, and Hashtable. As you might recognize, the framework satisfies the requirements for what we might refer to as classical data structures.
Not the purpose ...
However, it is not the purpose of this lesson to discuss either the Java Collections Framework, or classical data structures. Rather, they are mentioned here simply because the framework provides a good example of the use of the Object class as a generic type for runtime polymorphic behavior.
(Also beyond the scope of this lesson is the fact that the framework provides an outstanding example of the implementation of polymorphic behavior through the use of the Java interface. The use of the Java interface is a topic for a future lesson)References of type Object
In all cases, the classes mentioned above store references to objects created according to interfaces, contracts, and stipulations provided by the framework.
More importantly for the purposes of this lesson, however, those references are always stored as type Object.
The Object type is a completely generic type, which can be used to store a reference to any object that can be instantiated in Java.
Methods in the Object class
In an earlier lesson, I told you that the class named Object defines default versions of the following methods:
Because every class is either a direct or indirect subclass of Object, every class in Java, (including new classes that you define), inherits these eleven methods.
To be overridden ...
Most of these eleven methods are intended to be overridden for various purposes.
Invoking methods of the Object class
Now you know that you can store a reference to any object in a reference variable of type Object.
If you have read the previous lessons, you also know how runtime polymorphism based on class inheritance works.
Given the above, you should know that you can invoke any of the methods defined in the Object class on any reference to any object stored in a reference variable of type Object (including the references stored in the concrete implements of the Java Collections Framework).
And the behavior will be ...
If the class from which that object is instantiated inherits or defines an overridden version of one of the methods in the above list, invocation of that method on the reference will cause the overridden version to be executed.
Otherwise, invocation of that method on the reference will cause the default version defined in the Object class to be executed.
A sample program
This is illustrated in the program named Poly04, which you can view in its entirety in Listing 7 near the end of this lesson.
For purposes of illustration, this program deals specifically with the method named toString from the above list, but could deal just as well with the other methods in the list.
The class named A
Listing 1 defines a class named A, which extends the class named
Object
(recall
that this is the default extension, and it is not necessary to explicitly
show that a class extends Object).
class A extends Object{ //This class is empty }//end class A Listing 1 |
Does not override the toString method
The most important thing to note about the class named A is that it does not override any of the methods that it inherits from the class named Object.
For purposes of this illustration, we will say that it inherits the default version of the method named toString(), from the class named Object. (We will see an example of the behavior of the default version of that method shortly.)
The class named B
Listing 2 contains the definition of a class named B. This
class extends the class named A.
class B extends A{ public String toString(){ return "toString in class B"; }//end overridden toString() }//end class B Listing 2 |
Overrides the toString method
Of particular interest, for purposes of this lesson, is the fact that the class named B overrides the inherited toString() method.
(Although this is not particularly significant for the purposes of this lesson, it inherits the default version of the method, because its superclass named A, which extends Object, does not override the toString method.)Purpose of the toString method
The purpose of the toString() method is to return a reference to an object of the class String that represents an object instantiated from a class that overrides the method.
Here is part of what Sun has to say about the toString method:
"Returns a string representation of the object. In general, the toString method returns a string that "textually represents" this object. The result should be a concise but informative representation that is easy for a person to read. It is recommended that all subclasses override this method."Behavior of the overridden version
As you can see, I didn't follow Sun's advice very closely in this program. To begin with, I didn't override the toString method in the class named A.
Further, the behavior of my overridden version of the method in the class named B doesn't provide much in the way of a textual representation of an object instantiated from class B.
My overridden version simply returns a reference to a String object, containing text that indicates that the overridden version of the method in the class named B has been executed. (Of course, there wasn't much about an object instantiated from this class that could be represented in a textual way.)
Will be useful later
The reference to the String object returned by the overridden version of the method will prove useful later when we need to determine which version of the method is actually executed.
The class named C
Listing 3 shows the definition of a class named C, which extends
the class named B, and overrides the method named toString()
again. (An inherited method can be overridden by every class that
inherits it, resulting in potentially many different overridden versions
of a method in a class hierarchy.)
class C extends B{ public String toString(){ return "toString in class C"; }//end overridden toString() }//end class B Listing 3 |
Behavior of overridden version
The behavior of this overridden version of the method is similar to, but different from the overridden version in the class B.
In this case, the method returns a reference to a String object that can be used to confirm that this overridden version of the method has been executed.
The driver class
Finally, Listing 4 shows the beginning of the driver class named Poly04.
public class Poly04{ public static void main( String[] args){ Object varA = new A(); String v1 = varA.toString(); System.out.println(v1); Listing 4 |
A new object of the class A
The main method of the driver class begins by instantiating a new object of the class A, and saving the object's reference in a reference variable of type Object, named varA.
Invoke toString method on the reference
Then the code invokes the toString method on the reference variable named varA, saving the returned reference to the String in a reference variable of type String named v1.
Display the returned String
Finally, that reference is passed to the println method, causing the String returned by the toString method to be displayed on the computer screen. This causes the following text to be displayed:
A@111f71
Pretty ugly, huh?
Nowhere does our program explicitly show the creation of any text that looks anything like this. Where did it come from?
Default toString behavior
What you are seeing here is the String produced by the default version of the toString method, as defined by the class named Object. (Now you know why Sun recommends that all new classes override the toString method.)
Class A does not override toString
Recall that our new class named A does not override the toString method. Therefore, when the toString method is invoked on a reference to an object of the class A, the default version of the method is executed, producing output similar to that shown above.
What does Sun have to say?
Here is more of what Sun has to say about the default version of the toString method
"The toString method for class Object returns a string consisting of the name of the class of which the object is an instance, the at-sign character `@', and the unsigned hexadecimal representation of the hash code of the object."You should recognize this as a description of the output produced by invoking the toString method on the reference to the object of the class A, so that explains the ugliness (hexadecimal representations of hashcodes are usually pretty ugly).
A new object of the class B
Now consider the code shown in Listing 5, which instantiates a new object
of the class named B, and stores the object's reference in a
reference variable of type Object.
Object varB = new B(); String v2 = varB.toString(); System.out.println(v2); Listing 5 |
Invoke toString and display the result
The code in Listing 5 invokes the toString method on the reference of type Object, saving the returned reference in the reference variable named v2. (Recall that the toString method is overridden in the class named B.)
As before, the reference is passed to the println method, which causes the following text to be displayed on the computer screen.
toString in class B
Do you recognize this?
You should recognize this as the text that was encapsulated in the String object by the overridden version of the toString method defined in the class named B.
Overridden version of toString was executed
This verifies that even though the reference to the object of the class B was stored in a reference variable of type Object, the overridden version of the toString method in the class named B was executed (instead of the default version defined in the class named Object). This is runtime polymorphic behavior, as described in a previous lesson.
The selection of a method for execution is based on the actual type of object whose reference is stored in a reference variable, and not on the type of the reference variable on which the method is invoked.An object of the class C
Finally, the code in Listing 6
Object varC = new C(); String v3 = varC.toString(); System.out.println(v3); Listing 6 |
What will the output look like?
By now, you should know what to expect in the way of text appearing on the computer screen. The code in Listing 6 causes the following text to be displayed:
toString in class C
Overridden version of toString was invoked
This confirms what you should already have known by now. In particular, even though the reference to the object of the class C is stored in a reference variable of type Object, the overridden version of the toString method in the class named C was executed. Again, this is runtime polymorphic behavior based on class inheritance and method overriding.
No downcasting was required
It is also very important to note that no downcasting was required in order to invoke the toString() method in any of the cases shown above. Because a default version of the toString() method is defined in the Object class, that method can be invoked without a requirement for downcasting on a reference to any object stored in a variable of type Object. This holds true for any of the eleven methods defined in the class named Object.
From a practical programming viewpoint, polymorphism manifests itself in three distinct forms in Java:
More specifically, in this lesson, I have discussed the use of the Object class as a completely generic type for storing references to objects of subclass types, and have explained how that results in a very useful form of runtime polymorphism. .
I briefly mentioned the default version of the eleven methods defined in the Object class, and explained that in many cases, those default versions are meant to be overridden.
I provided a sample program, which illustrates the overriding of the toString() method, which is one of the eleven methods defined in the Object class.
/*File Poly04.java Copyright 2002, R.G.Baldwin This program illustrates polymorphic behavior Program output is: A@111f71 toString in class B toString in class C **************************************/ class A extends Object{ //This class is empty }//end class A //===================================// class B extends A{ public String toString(){ return "toString in class B"; }//end overridden toString() }//end class B //===================================// class C extends B{ public String toString(){ return "toString in class C"; }//end overridden toString() }//end class B //===================================// public class Poly04{ public static void main( String[] args){ Object varA = new A(); String v1 = varA.toString(); System.out.println(v1); Object varB = new B(); String v2 = varB.toString(); System.out.println(v2); Object varC = new C(); String v3 = varC.toString(); System.out.println(v3); }//end main }//end class Poly04 //===================================// Listing 7 |
Copyright 2002, Richard G. Baldwin. Reproduction in whole or in part in any form or medium without express written permission from Richard Baldwin is prohibited.
Richard has participated in numerous consulting projects involving Java, XML, or a combination of the two. He frequently provides onsite Java and/or XML training at the high-tech companies located in and around Austin, Texas. He is the author of Baldwin's Java Programming Tutorials, which has gained a worldwide following among experienced and aspiring Java programmers. He has also published articles on Java Programming in Java Pro magazine.
Richard holds an MSEE degree from Southern Methodist University and has many years of experience in the application of computer technology to real-world problems.
-end-