JavaEnterprise JavaThe Essence of OOP using Java, Runtime Polymorphism through Inheritance

The Essence of OOP using Java, Runtime Polymorphism through Inheritance

Developer.com content and product recommendations are editorially independent. We may make money when you click on links to our partners. Learn More.

Java Programming Notes #1612


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, Type Conversion, Casting, etc.
.

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.

In this lesson …

I will begin the discussion of runtime polymorphism through method overriding
and inheritance in this lesson.  I will cover interfaces in a subsequent
lesson.

The essence of runtime polymorphic behavior

With runtime polymorphism based on method overriding, the decision as
to which version of a method will be executed is based on the actual type
of object whose reference is stored in the reference variable, and not
on the type of the reference variable on which the method is invoked.

Late binding

The decision as to which version of the method to invoke cannot be made
at compile time.  That decision must be deferred and made at runtime. 
This is sometimes referred to as late binding.

Discussion
and Sample Code


Operational description of runtime polymorphism

Here is an operational description of runtime polymorphism as
implemented in Java through inheritance and method overriding:

  • Assume that a class named SuperClass defines a method named method.
  • Assume that a class named SubClass extends SuperClass and
    overrides the method named method.
  • Assume that a reference to an object of the class named SubClass
    is assigned to a reference variable named ref of type SuperClass.
  • Assume that the method named method is then invoked on the reference
    variable using the following syntax:  ref.method()
  • Result:  The version of the method named method
    that will actually be executed is the overridden version in the class named
    SubClass,
    and is not the version that is defined in the class named
    SuperClass.

This is runtime polymorphism, sometimes also referred to as late-binding.

Runtime polymorphism is very powerful

As you gain more experience with Java, you will learn that much of the
power of OOP using Java is centered on runtime polymorphism using class
inheritance, interfaces, and method overriding.  (The use of interfaces
for polymorphism will be discussed in a subsequent lesson.)

An important attribute of runtime polymorphism

The decision as to which version of the method to execute is based on
the actual type of object whose reference is stored in the reference variable,
not on the type of the reference variable on which the method is invoked.

Why is it called runtime polymorphism?

The reason that this type of polymorphism is often referred to as runtime
polymorphism is because the decision as to which version of the method
to execute cannot be made until runtime.  The decision cannot be made
at compile time.

Why defer the decision?

The decision cannot be made at compile time because the compiler has
no way of knowing (when the program is compiled) the actual type
of the object whose reference will be stored in the reference variable
.

In an extreme case, for example, the object might be deserialized at
runtime from a network connection of which the compiler has no knowledge.

Could be either type

For the situation described above, that deserialized object could just
as easily be of type SuperClass as of type SubClass
In either case, it would be valid to assign the object’s reference to the
same superclass reference variable.

If the object were of the SuperClass type, then an invocation
of the method named method on the reference would cause the version
of the method defined in SuperClass, and not the version defined
in SubClass, to be executed.  (The version executed is determined
by the type of the object and not by the type of the reference variable
containing the reference to the object.)

Sample Program

Let’s take a look at a sample program that illustrates runtime polymorphism
using class inheritance and overridden methods.  The name of the program
is Poly03.  A complete listing of the program is shown in Listing
7 near the end of the lesson.

Listing 1 shows the definition of a class named A, which extends
the class named Object.

(Remember that any class that doesn’t extend some other
class automatically extends Object by default, and it is not necessary
to show that explicitly as I did in this example.)

class A extends Object{
  public void m(){
    System.out.println("m in class A");
  }//end method m()
}//end class A

Listing 1

The class named A defines a method named m().

Behavior of the method

The behavior of the method, as defined in the class named A,
is to display a message indicating that it has been invoked, and that it
is defined in the class named A.

This message will allow us to determine which version of the method
is executed in each case discussed later.

The class named B

Listing 2 shows the definition of a class named B that extends
the class named A.

 

class B extends A{
  public void m(){
    System.out.println("m in class B");
  }//end method m()
}//end class B

Listing 2

The class named B overrides (redefines) the method named
m(),
which it inherits from the class named A.

Behavior of the overridden method

Like the inherited version, the overridden version displays a message
indicating that it has been invoked.  However, the message is different
from the message displayed by the inherited version discussed above. 
The overridden version tells us that it is defined in the class named B.
(The
behavior of the overridden version of the method is appropriate for an
object instantiated from the class named B.)

Again, this message will allow us to determine which version of the
method is executed in each case discussed later.

The driver class

Listing 3 shows the beginning of the driver class named Poly03.

 

public class Poly03{
  public static void main(
                        String[] args){
    Object var = new B();
    ((B)var).m();

Listing 3

A new object of the class B

Note that the code in the main method begins by instantiating
a new object of the class named B, and assigning the object’s reference
to a reference variable of type Object.

(Recall that this is legal because an object’s reference
can be assigned to any reference variable whose type is a superclass of
the class from which the object was instantiated.  The class named
Object
is the superclass of all classes.)

Downcast and invoke the method

If you read the previous lesson, it will come as no surprise to you
that the second statement in the main method, which casts the reference
down to type B and invokes the method named m() on it, will
compile and execute successfully.

Which version was executed?

The execution of the method produces the following output on the computer
screen:

m in class B

By examining the output, you can confirm that the version of the method
that was overridden in the class named B is the version that was
executed.

Why was this version executed?

This should also come as no surprise to you.  The cast converts
the type of the reference from type Object to type B.

You can always invoke a public method belonging to an object using a
reference to the object whose type is the same as the class from which
the object was instantiated.

Not runtime polymorphic behavior

Just for the record, the above invocation of the method does not constitute
runtime polymorphism (in my opinion).  I included that invocation
of the method to serve as a backdrop for what follows.

This is runtime polymorphic behavior

However, the following invocation of the method does constitute runtime
polymorphism.

The statement in Listing 4 casts the reference down to type A
and invokes the method named m() on that reference.  It may,
or may not come as a surprise to you that the invocation of the method
shown in Listing 4 also compiles and runs successfully.

 

    ((A)var).m();

Listing 4

The method output

Here is the punch line.  Not only does the statement in Listing
4 compile and run successfully, it produces the following output, (which
is exactly the same output as before):

m in class B

Same method executed in both cases

It is extremely important to note that this output, (produced by
casting the reference variable to type A instead of type B),
is exactly
the same as that produced by the earlier invocation of the method when
the reference was cast to type B.  This means that the same
version of the method was executed in both cases
.

This confirms that even though the type of the reference was converted
to type A, (rather than type Object or type B), the overridden
version of the method defined in class B was actually executed.

This is runtime polymorphic behavior.

The version of the method that was executed was based on the actual type
of the object, B, and not on the type of the reference, A
This is an extremely powerful and useful concept.

Another invocation of the method

Now take a look at the statement in Listing 5.  Will this statement
compile and execute successfully?  If so, which version of the method
will be executed?

 

    var.m();

Listing 5

Compiler error

The code in Listing 5 attempts, unsuccessfully, to invoke the method
named m() using the reference variable named var, which is
of type Object.  The result is a compiler error, which in JDK
1.3 reads partially as follows:

Poly03.java:40: cannot resolve symbol

symbol  : method m  ()

location: class java.lang.Object

    var.m();

       ^

Some important rules

The Object class does not define a method named m()
Therefore, the overridden method named m() in the class named B
is not an overridden version of a method that is defined in the class named
Object.

Necessary, but not sufficient

Runtime polymorphism based on class inheritance requires that the type
of the reference variable be a superclass of the class from which the object
(on
which the method will be invoked)
is instantiated.

However, while necessary, that is not sufficient.

The type of the reference variable must also be a class that either
defines or inherits the method that will ultimately be invoked on the object.

This method is not defined in the Object class

Since the class named Object does not define (or inherit)
the method named
m(), a reference of type Object does not
qualify as a participant in runtime polymorphic behavior in this case. 
The attempt to use it as a participant resulted in the compiler error given
above.

One additional scenario

Before leaving this topic, let’s look at one additional scenario to
help you distinguish what is, and what is not, runtime polymorphism. 
Consider the code shown in Listing 6.

 

    var = new A();
    ((A)var).m();    

Listing 6

A new object of type A

The code in Listing 6 instantiates a new object of the class named A,
and stores the object’s reference in the original reference variable named
var
of type Object.

(As a side note, this overwrites the previous contents of
the reference variable with a new reference and causes the object whose
reference was previously stored there to become eligible for garbage collection.)

Downcast and invoke the method

Then the code in Listing 6 casts the reference down to type A,
(the
type of the object to which the reference refers),
and invokes the
method named m() on the downcast reference.

The output

As you would probably predict, this produces the following output on
the computer screen:

m in class A

In this case, the version of the method defined in the class named A,
(not
the version defined in B)
was executed.

Not polymorphic behavior

In my view, this is not polymorphic behavior (at least it isn’t a
very useful form of polymorphic behavior)
.  This code simply converts
the type of the reference from type Object to the type of the class
from which the object was instantiated, and invokes one of its methods. 
Nothing special takes place regarding a selection among different versions
of the method.

Some authors may disagree

While some authors might argue that this is technically runtime polymorphic
behavior, in my view at least, it does not illustrate the real benefits
of runtime polymorphic behavior.  The benefits of runtime polymorphic
behavior generally accrue when the actual type of the object is a subclass
of the type of the reference variable containing the reference to the object.

Once again, what is runtime polymorphism?

As I have discussed in this lesson, runtime polymorphic behavior based
on inheritance occurs when

  • The type of the reference is a superclass of the class from which the object
    was instantiated
  • The version of the method that is executed is the version that is either
    defined in, or inherited into, the class from which the object was instantiated

More than you ever wanted to hear

And that is probably more than you ever wanted to hear about runtime
polymorphism based on inheritance.

A future lesson will discuss runtime polymorphism based on the Java
interface.  From a practical viewpoint, you will find the rules to
be similar but somewhat different in the case of the Java interface.

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

This lesson discusses method overriding through inheritance.

With runtime polymorphism based on method overriding, the decision as
to which version of a method will be executed is based on the actual type
of object whose reference is stored in the reference variable, and not
on the type of the reference variable on which the method is invoked.

The decision as to which version of the method to invoke cannot be made
at compile time.  That decision must be deferred and made at runtime. 
This is sometimes referred to as late binding.

This is illustrated in the sample program discussed in this lesson.

What’s Next?


In the next lesson, I will continue my discussion of the implementation
of polymorphism using method overriding through inheritance, and I will
concentrate on a special case in that category.

Specifically, I will discuss the use of the Object class as a
completely generic type for storing references to objects of subclass types,
and explain how that results in a very useful form of runtime polymorphism.

Complete Program Listing


A complete listing of the program is shown in Listing 7 below.

 

/*File Poly03.java
Copyright 2002, R.G.Baldwin

This program illustrates downcasting
and polymorphic behavior

Program output is:
  
m in class B
m in class B
m in class A
**************************************/

class A extends Object{
  public void m(){
    System.out.println("m in class A");
  }//end method m()
}//end class A
//===================================//

class B extends A{
  public void m(){
    System.out.println("m in class B");
  }//end method m()
}//end class B
//===================================//

public class Poly03{
  public static void main(
                        String[] args){
    Object var = new B();
    //Following will compile and run
    ((B)var).m();
    //Following will also compile 
    // and run due to polymorphic
    // behavior.
    ((A)var).m();
    //Following will not compile
    //var.m();
    //Instantiate obj of class A
    var = new A();
    //Invoke the method on it
    ((A)var).m();    
  }//end main
}//end class Poly03
//===================================//

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

Get the Free Newsletter!

Subscribe to Developer Insider for top news, trends & analysis

Latest Posts

Related Stories