Java Programming Notes #1614
Preface
This lesson is one of a series of lessons designed to teach you about the
essence of Object-Oriented Programming (OOP) using Java.
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.
Preview
What is polymorphism?
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:
- Method overloading
- Method overriding through inheritance
- Method overriding through the Java interface
I covered method overloading as one form of polymorphism (compile-time
polymorphism) in a previous lesson. I also explained automatic
type
conversion and the use of the cast operator for type conversion
in a previous lesson.
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.
Discussion
and Sample Code
The Java Collections Framework
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:
- clone()
- equals(Object obj)
- finalize()
- getClass()
- hashCode()
- notify()
- notifyAll()
- toString()
- wait()
- wait(long timeout)
- wait(long timeout, int nanos)
Every class inherits these 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
- Instantiates a new object of the class C
- Stores the object’s reference in a reference variable of type Object
- Invokes the toString method on the reference
- Displays the returned string on the computer screen
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.
Summary
Polymorphism manifests itself in Java in the form of multiple methods having
the same name.
From a practical programming viewpoint, polymorphism manifests itself
in three distinct forms in Java:
- Method overloading
- Method overriding through inheritance
- Method overriding through the Java interface
In this lesson, I have continued my discussion of the implementation of
polymorphism using method overriding through inheritance, and have concentrated
on a special case in that category.
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.
What’s Next?
In the next lesson, I will embark on an explanation of runtime polymorphic
behavior based on the Java interface and method overriding. In my
opinion, this is one of the most important concepts in Java OOP, and the
one that seems to give students the greatest amount of difficulty.
Complete Program Listing
A complete listing of the program is shown in Listing 7 below.
/*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 |
About the author
Richard Baldwin
is a college professor (at Austin Community College in Austin, TX) and
private consultant whose primary focus is a combination of Java and XML.
In addition to the many platform-independent benefits of Java applications,
he believes that a combination of Java and XML will become the primary
driving force in the delivery of structured information on the Web.
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.