Learning C# and OOP, Polymorphism and Interfaces, Part 3

Baldwin uses a sample program to illustrate some of the things that you can do with interfaces, along with some of the things that you cannot do with interfaces.

Published:  November 11, 2003
By Richard G. Baldwin

C# Programming Notes # 128c


Preface

This lesson is one of a series of lessons designed to teach you about Object-Oriented Programming (OOP) using C#.

The first lesson in the group was entitled Learning C# and OOP, Getting Started, 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 C#.  The previous lesson was entitled Learning C# and OOP, Polymorphism and Interfaces, Part 2.

Necessary and significant aspects

This miniseries will describe and discuss the necessary and significant aspects of OOP using C#.

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 C# programming language.

Comparisons with Java

Some of the lessons in this miniseries will also make comparisons between C# and Java with respect to both syntax and OOP concepts.  This is one of those lessons.  I will emphasize the similarities between these two programming languages, which are likely to dominate the programming world in the foreseeable future.  By studying these lessons and learning about OOP using C#, you will also be learning quite a lot about OOP using Java.

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 programming tutorials.  You will find a consolidated index at www.DickBaldwin.com. 

Preview

What is polymorphism?

The meaning of the word polymorphism is something like one name, many forms.

How does C# implement polymorphism?

Polymorphism manifests itself in C# 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 C#:

  • Method overloading
  • Method overriding through inheritance
  • Method overriding through the C# interface

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

I also discussed runtime polymorphism implemented through method overriding and class inheritance in a previous lesson.

I provided an introduction to the C# interface and began the discussion of interfaces and polymorphism in the two lessons prior to this one. 

The C# interface and polymorphism

In this lesson, I will continue the explanation of the C# interface and polymorphism.

As before, I will also compare the code illustrating the C# interface and polymorphism with similar Java code illustrating the Java interface and polymorphism.  The purpose of the comparison is to emphasize the strong similarities that exist between C# and Java.

A new relationship

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

I explained that because a class can implement many different interfaces, a single object in C# 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's reference. 

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

A sample program

In the previous lesson, I presented a skeleton program that introduced you to important aspects of polymorphic behavior based on the C# interface.

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

The sample program that I will discuss in this lesson will illustrate some of the things that you can do with interfaces, along with some of the things that you cannot do with interfaces.

Discussion and Sample Code

Let's take a look at the sample program named Poly06.  This program is designed to be very simple, while still illustrating runtime polymorphism using interfaces, class inheritance, and overridden methods.

As mentioned above, I will compare this C# program with a Java program to emphasize the strong similarities between C# and Java.

Will discuss in fragments

I will discuss this program in fragments.  You can view a complete listing of the C# program in Listing C15 near the end of the lesson.  You can view a complete listing of the Java program in Listing J15 near the end of the lesson.

Same structure as before

This program has the same structure as the skeleton program named Poly05, which was discussed in the previous lesson.

(I strongly recommend that you study the previous lesson before continuing with this lesson if you haven't already done so.)

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 of the program, something happens.

(Admittedly not much happens.  Text is displayed on the computer screen, but that is something.)

The interface definitions

Listing C1 shows the definitions for two C# interfaces named I1 and I2.
 
interface I1{
  void p();
}//end interface I1
//===================================//

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

Listing C1

Similarly, Listing J1 shows the definitions for two Java interfaces having the same names and same declared methods.
 
interface I1{
  public void p();
}//end interface I1

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

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

Listing J1

Identical to program in previous lesson

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 in the previous lesson.  I discussed these interfaces in detail in that lesson, and won't repeat that discussion here.

The class named A

Listing C2 shows the definition of the C# class named A along with the definition of the method named x, and the overridden method named ToString.
 
class A{
  public override String ToString(){
    return "ToString in A";
  }//end ToString()
  //---------------------------------//
  
  public String x(){
    return "x in A";
  }//end x()
  //---------------------------------//
}//end class A

Listing C2

Listing J2 shows the definition of a corresponding class named A in the Java program.
 
class A{
  public String toString(){
    return "toString in A";
  }//end toString()
  //---------------------------------//
  
  public String x(){
    return "x in A";
  }//end x()
  //---------------------------------//
}//end class A

Listing J2

The methods named ToString and x were also fully defined and fully discussed in the program in the previous lesson, so they won't be discussed here.

The method named B

Listing C3 defines the C# class named B, which extends A, and implements I2.
 
class B : A,I2{
  public void p(){
    System.Console.WriteLine("p in B");
  }//end p()
  //---------------------------------//
  
  public void q(){
    System.Console.WriteLine("q in B");
  }//end q();
  //---------------------------------//
}//end class B

Listing C3

Actually implements two interfaces

Although it isn't obvious from an examination of Listing C3 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 into that interface.

Must define two methods

As a result, the class named B must provide concrete definitions for the methods named 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 C3, the behavior of each of these methods is simply to display a message indicating that the method has been executed.  This will be useful later to tell us exactly which method is executed when we exercise the objects from the main method of a driver class.

A similar Java class named B

Listing J3 shows a similar Java class.  Except for some minor syntax differences, the Java class is identical to the C# class shown in Listing C3.
 
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 J3

The class named C

Listing C4 shows the C# class named C.
 
class C : I2{
  public void p(){
    System.Console.WriteLine("p in C");
  }//end p()
  //---------------------------------//
  
  public void q(){
    System.Console.WriteLine("q in C");
  }//end q();
  //---------------------------------//
}//end class B

Listing C4

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.

Different behavior

It is important to note that the behaviors of the interface methods named p and q as defined in the class named C are different from the behaviors of those same interface methods as defined in the class named B.

The classes named B and C both implement the interface named I2.  Therefore, both classes must define the methods named p and q.  However, there is no requirement that the two classes define the methods to have the same behavior.

A similar Java class named C

Listing J4 shows a similar Java class. Again, except for some minor syntax differences, the Java class is identical to the C# class
shown in Listing C4.
 
class C 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 J4

The driver class

Now we are ready to get down to business and discuss the reasons that this lesson exists in the first place.

Listing C5 shows the beginning of a C# 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.

(Listing C5 shows only the beginning portion of the Main method.)

public class Poly06{
  public static void Main(){
    I1 var1 = new B();
    var1.p();//OK

Listing C5

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 interface type.

A new object of the class B

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

Important: stored as type I1

It is important to note that the code in Listing C5 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 C5 successfully invokes the method named p on the reference to the object of type B, producing the screen output shown in Figure 1.
 
p in B

Figure 1

Why is this allowed?

This is allowed because the method named p is declared in the interface named I1.  The class named B indirectly implements the interface named I1 because it directly implements I2, which extends I1.

Therefore, the reference to an object of the class named B can be treated as the interface type I1, and the method declared in the interface named I1 can be invoked on that reference.

Which version of the method was executed?

It is 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.

(This is an example of runtime polymorphism.  With runtime polymorphism, the actual method selected for execution is based on the class from which the object was instantiated, and not from the type of the reference variable that contains the object's reference.)

A similar Java driver class

Listing J5 shows the beginning of a similar Java driver class.  Except for some minor syntax differences, the code in Listing J5 is identical to the C# code shown in Listing C5.
 
public class Poly06{
  public static void main(
                        String[] args){
    I1 var1 = new B();
    var1.p();//OK 

Listing J5

Attempt unsuccessfully to invoke q

Next, the code in Listing C6 attempts, unsuccessfully, to invoke the method named q on the same reference variable of type I1.

(Note that the statement in Listing C6 was marked as a comment in order to make it possible to compile the program.)

    //var1.q();//won't compile

Listing C6

Why did the method invocation 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.

(Even though the method actually exists in the object instantiated from the class named B, only those methods declared in, or inherited into the interface named I1 can be invoked when the object's reference is treated as the interface type I1.)

Similar Java code

Listing J6 contains a Java statement that behaves identically to the C# statement in Listing C6.
 
    //var1.q();//won't compile

Listing J6

The solution is a type conversion

Listing C7 shows the solution to the problem presented by the code in Listing C6.
 
    ((I2)var1).q();//OK

Listing C7

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

(The method named q is defined in the class named B, and is declared in the interface named I2.  Changing the type of the reference to either B or I2 is a solution to the problem in this case.)

The solution that was used

The solution that was used takes the form of using a cast operator to convert the type of the reference from type I1, to type I2.  Then it becomes possible to invoke the method named q on that reference of type I2.

(This works because the class named B implements the interface named I2 and the interface named I2 declares the method named q.)

The output

The code in Listing C7 produces the output shown in Figure 2.
 
q in B

Figure 2

Similar Java code

The Java code in Listing J7 behaves exactly like the C# code in Listing C7.  The code in Listing J7 provides the same solution to the same problem.
 
    ((I2)var1).q();//OK

Listing J7

Using type I2 directly

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

Listing C8

Invoke both methods successfully

Then the code in Listing C8 successfully invokes the methods named p and q on the object's reference, producing the output shown in Figure 3.
 
p in B
q in B

Figure 3

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 the methods p and q.

Because the class named B implements I2, the reference to an object of type B can be stored in a reference variable of type I2.

Because I2 inherits p and declares q, either or both of those methods can be invoked on the reference that is stored as the type I2.

The behavior of the methods

As always, the behavior of the methods is determined by the definitions of the methods defined in or inherited into the class from which the object was instantiated.

Similar Java code

Listing J8 contains Java code that behaves identically to the C# code in Listing C8.
 
    I2 var2 = new B();
    var2.p();//OK
    var2.q();//OK

Listing J8

Attempt, unsuccessfully, to invoke x on var2

Following this, the code in Listing C9 attempts, unsuccessfully, to invoke the method named x on the reference variable named var2 of type I2.  This code produces a compiler error.

(As before, the code was marked as a comment to make it possible to compile the program.)

    //Following code won't compile
    //String var3 = var2.x();

Listing C9

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, so the object does contain a method named x.

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 to type A (where the method named x is defined) or
  • Converted to type B (where the method named x is inherited).

Similar Java code

The Java code in Listing J9 behaves identically to the C# code in Listing C9.
 
    //Following code won't compile
    //String var3 = var2.x();

Listing J9

Do the type conversion

The type conversion required to solve the problem shown in Listing C9 is accomplished in Listing C10.  The code in Listing C10 temporarily converts the reference to type A using a cast operator.

(It would also work to cast the reference to type B.)

    String var3 = ((A)var2).x();//OK
    System.Console.WriteLine(var3);

Listing C10

The String produced by the first statement in Listing C10 is passed to the WriteLine method in the second statement in Listing C10 causing the text shown in Figure 4 to be displayed on the computer screen.
 
x in A

Figure 4

Similar Java code

The Java code in Listing J10 behaves identically to the C# code in Listing C10, producing the same output as that shown in Figure 4.
 
    String var3 = ((A)var2).x();//OK
    System.out.println(var3);

Listing J10

Get ready for a surprise

At this point, if you feel like you understand 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 C11 successfully invokes the overridden ToString method on the object of the class B whose reference is stored as type I2.

(Recall that the ToString method inherited from the Object class was overridden in the class named B.)

    var3 = var2.ToString();//OK
    System.Console.WriteLine(var3);

Listing C11

How can this work?

How can this work when the interface named I2 doesn't obviously declare or inherit a method named ToString.

A subtle difference in behavior

Recall that I told you in one of the previous lessons that although an interface cannot directly extend any class, it does implicitly extend the class named Object.

Can invoke Object methods

As a result, any of the methods defined in the Object class can be invoked on a reference to any object when the reference is stored as an interface type (such as I2).

With respect to the 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 C11 produce the screen output shown in Figure 5.

(Recall that this is the String that is returned by the overridden version of the ToString method as defined in the class named A and inherited into the class named B.)

ToString in A

Figure 5

Polymorphism applies

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 C11 was the overridden version defined in class A, and was not the default version defined in the Object class.  The overridden version in class A was inherited into class B.

Similar Java code

The Java code in Listing J11 behaves identically to the C# code in Listing C11, producing the appropriate output for the overridden toString method.
 
    var3 = var2.toString();//OK
    System.out.println(var3);

Listing J11

The reverse of the above 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 C12 instantiates a new object of type B and stores it in a reference of type Object.

Attempt unsuccessfully to invoke p

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

Listing C12

Same song, an even different verse

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

(As before, I marked the second statement in Listing C12 as a comment in order to make it possible to compile the program.)

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

A similar Java statement

The Java code in Listing J12 is essentially the same as the C# code in Listing C12.
 
    Object var4 = new B();
    //var4.p();//won't compile

Listing J12

Convert reference to type I1

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

Listing C13

The output

The code in Listing C13 compiles and executes successfully, producing the screen output shown in Figure 6.
 
p in B

Figure 6

Similar Java code

At the risk of becoming extremely boring, the Java code in Listing J13 is essentially the same as the C# code in Listing C13.
 
    ((I1)var4).p();//OK

Listing J13

The point in boring you with this Java code is to emphasize that if you understand interfaces in Java, you also understand interfaces in C#.  Similarly, if you understand interfaces in C#, you also understand interfaces in Java.

Although there are some syntax differences between the use of interfaces in C# and Java, there are essentially no conceptual differences between the two.

A walk in the park

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

Listing C14

By now, you should know exactly why the C# code in Listing C14 (and the Java code in Listing J14) produces the screen output shown in Figure 7.
 
p in C
q in C

Figure 7

A new object of the class named C

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

(In the process, it overwrites the reference previously stored in var2.)

Then the code in Listing C14 successfully invokes the methods named p and q on the object's reference.

Why is this possible?

This is possible because the class named C implements I2, which extends I1.

Which methods were executed?

The output shown in Figure 7 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).

(Remember, with runtime polymorphism, the method selected for execution is the method defined in, or inherited into, the class from which the object was instantiated.)

Same method name, different behavior

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

The power of the C# 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 a 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 for an object of the class from which that particular object was instantiated.

This is runtime polymorphism based on interfaces and overridden methods.

Java code again

Need I say it again?  The behavior of the Java code shown in Listing J14 is the same as the behavior of the C# code shown in Listing C14.
 
    var2 = new C();
    var2.p();//OK
    var2.q();//OK

Listing J14

Summary

If you don't understand interfaces ...

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

Interfaces are indispensable in C#

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

What can you do with interfaces?

The sample program that I discussed in this lesson has illustrated 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?

C# 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 C#.

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 C# program is shown in Listing C15 below.  A complete listing of the Java program is shown in Listing J15 below.

/*File Poly06.cs
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
**************************************/
using System;

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

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

class A{
  public override String ToString(){
    return "ToString in A";
  }//end ToString()
  //---------------------------------//
  
  public String x(){
    return "x in A";
  }//end x()
  //---------------------------------//
}//end class A
//===================================//

class B : A,I2{
  public void p(){
    System.Console.WriteLine("p in B");
  }//end p()
  //---------------------------------//
  
  public void q(){
    System.Console.WriteLine("q in B");
  }//end q();
  //---------------------------------//
}//end class B
//===================================//

class C : I2{
  public void p(){
    System.Console.WriteLine("p in C");
  }//end p()
  //---------------------------------//
  
  public void q(){
    System.Console.WriteLine("q in C");
  }//end q();
  //---------------------------------//
}//end class B
//===================================//

public class Poly06{
  public static void Main(){
    I1 var1 = new B();
    var1.p();//OK
    //var1.q();//won't compile
    ((I2)var1).q();//OK
    //blank line
    System.Console.WriteLine("");
    
    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.Console.WriteLine(var3);
    var3 = var2.ToString();//OK
    System.Console.WriteLine(var3);
  	//blank line
    System.Console.WriteLine("");
    
    Object var4 = new B();
    //var4.p();//won't compile
    ((I1)var4).p();//OK
    //blank line
    System.Console.WriteLine("");
    
    var2 = new C();
    var2.p();//OK
    var2.q();//OK
    //blank line
    System.Console.WriteLine("");
  }//end main
}//end class Poly06
//===================================//


Listing C15

 
/*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{
  
  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 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 J15


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, Texas) and private consultant whose primary focus is a combination of Java, C#, and XML. In addition to the many platform and/or language independent benefits of Java and C# applications, he believes that a combination of Java, C#, and XML will become the primary driving force in the delivery of structured information on the Web.

Richard has participated in numerous consulting projects and he frequently provides onsite training at the high-tech companies located in and around Austin, Texas.  He is the author of Baldwin's Programming Tutorials, which has gained a worldwide following among experienced and aspiring programmers. He has also published articles in JavaPro 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.

-end-


1996, 1997, 1998, 1999, 2000, 2001, 2002 Richard G. Baldwin