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

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

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:

  • Method overloading
  • Method overriding through inheritance
  • Method overriding through the Java interface

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:

  • The interface named I2 declares the method named q
  • The interface named I2 inherits the declaration of the method named
    p
  • The class named B implements the interface named I2 and provides
    concrete definitions of both the methods p and q.

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:

  • The class in which the method is defined
  • An interface that declares the method, which is implemented by the class
    in which the method is defined
  • A couple of other possibilities involving subclasses or sub-interfaces

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.

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.

[email protected]

-end-

Latest Posts

Related Stories