November 22, 2014
Hot Topics:

The Essence of OOP Using Java, Polymorphism and the Object Class

  • March 14, 2002
  • By Richard G. Baldwin
  • Send Email »
  • More Articles »

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.

baldwin.richard@iname.com




Comment and Contribute

 


(Maximum characters: 1200). You have characters left.

 

 


Enterprise Development Update

Don't miss an article. Subscribe to our newsletter below.

Sitemap | Contact Us

Rocket Fuel