The Essence of OOP using Java, Polymorphism and Interfaces, Part 2

Baldwin uses a sample program to illustrate (in a very basic way) some of the things that you can do with interfaces, along with some of the things that you cannot do with interfaces.

Published:  April 10, 2002
By Richard G. Baldwin

Java Programming Notes # 1618


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, Polymorphism and Interfaces, Part 1.

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.

What is OOP?

OOP is the common abbreviation for Object-Oriented Programming.

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:

Preview

Method overloading

I covered method overloading as one form of polymorphism (compile-time polymorphism) in a previous lesson.

Method overriding and class inheritance

I discussed runtime polymorphism implemented through method overriding and class inheritance in more than one previous lesson.

Using the Java interface

In this and the previous lesson, I am explaining runtime polymorphism as implemented using method overriding and the Java interface.

A very important concept

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.  Therefore, I am trying to take it slow and easy.  As usual, I am illustrating the concept using sample programs.

A skeleton program

In the previous lesson, I presented a simple skeleton program that illustrated many of the important aspects of polymorphic behavior based on the Java interface.

Multiple inheritance and the cardinal rule

I explained how the implementation of interfaces in Java is similar to multiple inheritance.

I explained the cardinal rule of interface implementation.

A new relationship

I explained that objects instantiated from classes that implement the same interface have a new relationship that goes beyond the relationship imposed by the standard class hierarchy.

One object, many types

I explained that due to the combination of the class hierarchy and the fact that a class can implement many different interfaces, a single object in Java can be treated as many different types.  However, for any given type, there are restrictions on the methods that can be invoked on the object.

Many classes, one type

I explained that because different classes can implement the same interface, objects instantiated from different classes can be treated as a common interface type.

Interfaces are critical to Java programming

I suggested that there is little if anything useful that can be done in Java without understanding and using interfaces.

In support of this suggestion, I discussed several real-world examples of the use of the Java interface, including the Delegation Event Model and Remote Method Invocation.

Another sample program

In this lesson, I will present another sample program that will take you deeper into the world of polymorphism as implemented using the Java interface.

The sample program that I will discuss in this lesson will illustrate (in a very basic form) some of the things that you can do with interfaces, along with some of the things that you cannot do with interfaces.  In order to write programs that do something worthwhile, you will need to extend the concepts illustrated by this sample program into real-world requirements.

Discussion and Sample Code

Now, let's take a look at a sample program named Poly06 that is much simpler than any of those discussed in the real-world examples of the previous lesson.

This program is designed to be very simple, while still illustrating runtime polymorphism using interfaces, class inheritance, and overridden methods.

You can view a complete listing of the program in Listing 15 near the end of the lesson.

Same structure as before

Note that this program has the same structure as Poly05 discussed in the previous lesson.  (I strongly recommend that you study the previous lesson before continuing with this lesson.)  However, unlike the program in the previous lesson, the methods in this version of the program are not empty.  When a method is invoked in this version, something happens.  (Admittedly not much happens.  Text is displayed on the computer screen, but that is something.)

The interface definitions

Listing 1 shows the definition of the two interfaces named I1 and I2.
 
interface I1{
  public void p();
}//end interface I1

//===================================//

interface I2 extends I1{
  public void q();
}//end interface I2

Listing 1

Since the methods declared in an interface are not allowed to have a body, these interface definitions are identical to those shown in the program from the previous lesson.

The class named A

Similarly, Listing 2 shows the definition of the class named A along with the definition of the method named x, and the overridden method named toString.
 
class A extends Object{
  
  public String toString(){
    return "toString in A";
  }//end toString()
  //---------------------------------//
  
  public String x(){
    return "x in A";
  }//end x()
  //---------------------------------//
}//end class A

Listing 2

These two methods were also fully defined in the program from the previous lesson, so there is no change here either.

The method named B

Listing 3 defines the class named B, which extends A, and implements I2.
 
class B extends A implements I2{
  public void p(){
    System.out.println("p in B");
  }//end p()
  //---------------------------------//
  
  public void q(){
    System.out.println("q in B");
  }//end q();
  //---------------------------------//
}//end class B

Listing 3

Actually implements two interfaces

Although it isn't obvious from an examination of Listing 3 alone, the class named B actually implements both I2 and I1.  This is because the interface named I2 extends I1.  Thus, the class named B implements I2 directly, and implements I1 through interface inheritance.

The cardinal rule

In case you have forgotten it, the cardinal rule for implementing interfaces is:

If a class implements an interface, it must provide a concrete definition for all the methods declared by that interface, and all the methods inherited by that interface.  Otherwise, the class must be declared abstract and the definitions must be provided by a class that extends the abstract class.
Must define two methods

As a result, the class named B must provide concrete definitions for the methods p and q(The method named p is declared in interface I1 and the method named q is declared in interface I2.)

As you can see from Listing 3, the behavior of each of these methods is to display a message indicating that it has been executed.  This will be useful later to tell us exactly which method is executed when we exercise the objects in the main method of the driver class.

The class named C

Listing 4 shows the upgraded version of the class named C.
 
class C extends Object implements I2{
  public void p(){
    System.out.println("p in C");
  }//end p()
  //---------------------------------//
  
  public void q(){
    System.out.println("q in C");
  }//end q();
  //---------------------------------//
}//end class B

Listing 4

In this upgraded version, the methods named p and q each display a message indicating that they have been executed.  Again, this will be useful later to let us know exactly which version of the methods named p and q get executed when we exercise the objects.

The driver class

Listing 5 shows the beginning of the class named Poly06.  The main method in this class instantiates objects of the classes named B and C, and exercises them to illustrate what can, and what cannot be done with them.
 
public class Poly06{
  public static void main(
                        String[] args){
    I1 var1 = new B();
    var1.p();//OK 

Listing 5

A new data type

As explained in the previous lesson, when you define a new interface, you create a new data type.

You can store the reference to any object instantiated from any class that implements the interface in a reference variable of that type.

A new object of the class B

The code shown in Listing 5 instantiates a new object of the class B.

Important: stored as type I1

It is important to note that the code in Listing 5 stores the object's reference in a reference variable of the interface type I1 (not as the class type B).

Invoke an interface method

Following this, the code in Listing 5 successfully invokes the method named p on the reference, producing the following output on the computer screen:

p in B

Why is this allowed?

This is allowable because the method named p is declared in the interface named I1.

Which version of the method was executed?

It is also important to note, (by observing the output), that the version of the method defined in the class named B (and not the version defined in the class named C) was actually executed.

Attempt unsuccessfully to invoke q

Next, the code in Listing 6 attempts, unsuccessfully, to invoke the method named q on the same reference variable of type I1.
 
    var1.q();//won't compile

Listing 6

Why did it fail?

Even though the class named B, from which the object was instantiated, defines the method named q, that method is neither declared nor inherited into the interface named I1.

Therefore, a reference of type I1 cannot be used to invoke the method named q.

The solution is a type conversion

Listing 7 shows the solution to the problem presented by Listing 6.
 
    ((I2)var1).q();//OK

Listing 7

As in the case of polymorphism involving class inheritance, the solution is to change the type of the reference to a type that either declares or inherits the method named q.

In this case, this takes the form of using a cast operator to convert the type of the reference from type I1, to type I2, and then invoking the method named q on that reference of a new type.

This produces the following output:

q in B

Using type I2 directly

Listing 8 instantiates a new object of the class B and stores the object's reference in a reference variable of the interface type I2.
 
    I2 var2 = new B();
    var2.p();//OK
    var2.q();//OK

Listing 8

Invoke both methods successfully

Then the code successfully invokes both the methods p and q on that reference, producing the following output:

p in B
q in B

So, why does this work?

This works because:

Attempt, unsuccessfully, to invoke x on var2

Following this, the code in Listing 9 attempts, unsuccessfully, to invoke the method named x on the reference variable named var2 of type I2.  This code produces a compiler error.
 
    String var3 = var2.x();

Listing 9

The object of class B has a method named x

At this point, the reference variable named var2 contains a reference to an object instantiated from the class named B.

Furthermore, the class named B inherits the method named x from the class named A.

Necessary, but not sufficient

However, the fact that the object contains the method is not sufficient to make it executable in this case.

Same song, different verse

The interface named I2 neither declares nor inherits the method named x.

Therefore, the method named x cannot be invoked using the reference stored in the variable named var2 unless the reference is converted either to type A (where the method named x is defined) or type B (where the method named x is inherited).

Do the type conversion

The required type conversion is accomplished in Listing 10 where the reference is temporarily converted to type A using a cast operator. (It would also work to cast it to type B.)
 
    String var3 = ((A)var2).x();//OK
    System.out.println(var3);

Listing 10

The String produced by the first statement in Listing 10 is passed to the println method causing the following text to be displayed on the computer screen:

x in A

Get ready for a surprise

If you have now caught onto the general scheme of things, the next thing that I am going to show you may result in a little surprise.

Successfully invoke the toString method on var2

The first statement in Listing 11 successfully invokes the toString method on the object of the class B whose reference is stored as type I2.
 
    var3 = var2.toString();//OK
    System.out.println(var3);

Listing 11

How can this work?

How can this work when the interface named I2 neither declares nor inherits a method named toString.

A subtle difference in behavior

I am unable to point you to any Sun documentation to verify the following (I also admit that I haven't spent a large amount of time searching for such documentation).

With respect to the eleven methods declared in the Object class (listed in an earlier lesson), a reference of an interface type acts like it is also of type Object.

And the end result is ...

This allows the methods declared in the Object class to be invoked on references held as interface types without a requirement to cast the references to type Object. (Later, I will show you that the reverse is not true.)

The output

Therefore, the two statements shown in Listing 11 cause the following to be displayed on the computer screen:

toString in A

Polymorphism applies

Note that the object whose reference is held in var2 was instantiated from the class named B, which extends the class named A.

Due to polymorphism, the toString method that was actually executed in Listing 11 was the overridden version defined in class A, and not the default version defined in the Object class.  The overridden version in class A was inherited into class B.

The reverse is not true

While a reference of an interface type also acts like type Object, a reference of type Object does not act like an interface type.

Store a reference as type Object

The code in Listing 12 instantiates a new object of type B and stores it in a reference of type Object.

Attempt unsuccessfully to invoke p

Then it attempts, unsuccessfully, to invoke the method named p on the reference.
 
    Object var4 = new B();
    var4.p();//won't compile

Listing 12

Same song, an even different verse

The code in Listing 12 won't compile, because the Object class neither defines nor inherits the method named p.

In order to invoke the method named p on the reference of type Object, the type of the reference must be changed to either:

Convert reference to type I1

The code in Listing 13 uses a cast operator to convert the reference from type Object to type I1, and invokes the method named p on the converted reference.
 
    ((I1)var4).p();//OK

Listing 13

The output

The code in Listing 13 compiles and executed successfully, producing the following text on the computer screen:

p in B

A walk in the park

If you understand all of the above, understanding the code in Listing 14 should be like a walk in the park on a sunny day.
 
    var2 = new C();
    var2.p();//OK
    var2.q();//OK

Listing 14

Class C implements I2

Recall that the class named C also implements the interface named I2.

The code in Listing 14 instantiates a new object of the class named C, and stores the object's reference in the existing reference variable named var2 of type I2.

Then it invokes the methods named p and q on that reference, causing the following text to be displayed on the computer screen:

p in C
q in C

Which methods were executed?

This confirms that the methods that were actually executed were the versions defined in the class named C (and not the versions defined in the class named B).

Same method name, different behavior

It is important to note that the behavior of the methods named p and q, as defined in the class named C, is different from the behavior of the methods having the same signatures defined in the class named B.  Therein lies much of the power of the Java interface.

The power of the Java interface

Using interface types, it is possible to collect many objects instantiated from many different classes (provided all the classes implement a common interface), and store each of the references in some kind of collection as the interface type.

Appropriate behavior

Then it is possible to invoke any of the interface methods on any of the objects whose references are stored in the collection.

To use the current jargon, when a given interface method is invoked on a given reference, the behavior that results will be appropriate to the class from which that particular object was instantiated.

This is runtime polymorphism based on interfaces and overridden methods.

Summary

If you don't understand interfaces ...

If you don't understand interfaces, you don't understand Java, and it is highly unlikely that you will be successful as a Java programmer.

Interfaces are indispensable in Java

Beyond writing "Hello World" programs, there is little if anything that can be accomplished using Java without understanding and using interfaces.

What can you do with interfaces?

The sample program that I discussed in this lesson has illustrated (in a very basic form) some of the things that you can do with interfaces, along with some of the things that you cannot do with interfaces.  In order to write programs that do something worthwhile, you will need to extend the concepts illustrated by this sample program into real-world requirements.

What's Next?

Java supports the use of static member variables and static methods in class definitions.

While static members can be useful in some situations, the existence of static members tends to complicate the overall object-oriented structure of Java.

Furthermore, the overuse of static members can lead to problems similar to those experienced in languages like C and C++ that support global variables and global functions.

The use of static members will be discussed in the next lesson.

Complete Program Listing

A complete listing of the sample program is shown in Listing 15 below.
 
/*File Poly06.java
Copyright 2002, R.G.Baldwin

This program illustrates polymorphic 
behavior using interfaces in addition
to class inheritance.

The program output is:
p in B
q in B

p in B
q in B
x in A
toString in A

p in B

p in C
q in C
**************************************/

interface I1{
  public void p();
}//end interface I1
//===================================//

interface I2 extends I1{
  public void q();
}//end interface I2
//===================================//

class A extends Object{
  
  public String toString(){
    return "toString in A";
  }//end toString()
  //---------------------------------//
  
  public String x(){
    return "x in A";
  }//end x()
  //---------------------------------//
}//end class A
//===================================//

class B extends A implements I2{
  public void p(){
    System.out.println("p in B");
  }//end p()
  //---------------------------------//
  
  public void q(){
    System.out.println("q in B");
  }//end q();
  //---------------------------------//
}//end class B
//===================================//

class C extends Object implements I2{
  public void p(){
    System.out.println("p in C");
  }//end p()
  //---------------------------------//
  
  public void q(){
    System.out.println("q in C");
  }//end q();
  //---------------------------------//
}//end class B
//===================================//

public class Poly06{
  public static void main(
                        String[] args){
    I1 var1 = new B();
    var1.p();//OK
    //var1.q();//won't compile
    ((I2)var1).q();//OK
    System.out.println("");//blank line
    
    I2 var2 = new B();
    var2.p();//OK
    var2.q();//OK
    //Following won't compile
    //String var3 = var2.x();
    String var3 = ((A)var2).x();//OK
    System.out.println(var3);
    var3 = var2.toString();//OK
    System.out.println(var3);
    System.out.println("");//blank line
    
    Object var4 = new B();
    //var4.p();//won't compile
    ((I1)var4).p();//OK
    System.out.println("");//blank line
    
    var2 = new C();
    var2.p();//OK
    var2.q();//OK
    System.out.println("");//blank line
  }//end main
}//end class Poly06
//===================================//



Listing 15

 


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.

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

-end-