Polymporphism

Today's Challenge: can we have multiple implementations of one interface?

Recall that there are four basic mechanisms / ideas underlying object oriented programming:

  1. encapsulation
  2. information hiding
  3. inheritance
  4. polymorphism
Encapsulation (bundling together data and the functions that operate on that data) and information hiding (lanugage mechansims to enforce the separation of interface from implementation) "solved" the first of our three problems with Procedural Programming not giving us full separation of interface from implementation: the "what to do with structs" problem. We've covered both of these over the last few weeks. We've also covered inheritance as a mechanism to modify or extend functionality. It solves the second of the three problems with Procedural Programming not giving us full separation of interface from implementation: modifying or extending functionality without having to access implementation. Today we will start learning about polymorphism, which is the last of the four basic mechanisms/ideas behind object oriented programming. It solves our last "problem": how to allow multiple implementations for the same interface.

Inheritance: following "is-a" to its logical conclusion

Let's consider our old friends Point and LabPoint. You can take a look at Point.java and LabPoint.java if you like, but let's focus on just two things: their constructors and their toString()'s.

Point's Interface
public class Point {
  private double x, y;

  public Point(double x, double y) {
    this.x = x;
    this.y = y;
  }

  public String toString() {
    return x + " " + y;
  }
}
LabPoint's Interface
public class LabPoint extends Point {
  private String lab;

  public LabPoint(double x, double y, String lab) {
    super(x, y);
    this.lab = lab;
  }

  public String toString() {
    return super.toString() + " " + lab;
  }
}

For starters, let's consider a simple program involving only Point objects.

Ex1.java
import java.util.*;

public class Ex1 {
  public static void main(String[] args) {
    Random rand = new Random(System.currentTimeMillis());
    Point v  = new Point(3, 4);
    Point w  = new Point(5, 2);
    Point u;

    if( rand.nextBoolean() )
      u = v;
    else
      u = w;

    System.out.println(u.toString());
  }
}

Hopefully you see that what this program does is instantiate two points [(3,4) and (5,2)], and then randomly choose one of the two and assign reference u to point to it. You run this program and you might see "3 4" printed out. Run it again, and you might see "5 2" instead. It's random.

Now, recall that inheritance is supposed to give us an "is-a" relationship, as in "an object of type LabPoint is-a Point". If that's true, then the variable u should be able to point to a LabPoint just as much as it can point to a Point. So let's try it. The following program, Ex2.java, is just like the earlier program, except that the second "Point" w we instantiate is actually the LabPoint (5,2) with label "A", rather than just the plain-old Point (5,2). The code compiles, which certainly proves that the compiler is at peace with the prospect of the variable u referencing a LabPoint rather than a plain-old Point.

Ex2.java
import java.util.*;

public class Ex2 {
  public static void main(String[] args) {
    Random   rand = new Random(System.currentTimeMillis());
    Point    v    = new Point(3, 4);
    LabPoint w    = new LabPoint(5, 2, "A");
    Point    u;

    if( rand.nextBoolean() )
      u = v;
    else
      u = w;

    System.out.println(u.toString());
  }
}

Now, here's the $24,000 question: when I run this program and the random choice is to set u = w, what gets printed out? Do I get "5 2" — since u is declared as a reference to a Point — or do I get "5 2 A" — since the object u refers to is actually a LabPoint? What do you think? The answer is ... drum-roll please ... "5 2 A"!

~/$ java Ex2
5.0 2.0 A
This is actually pretty amazing. "Why?", you ask? Because the the compiler didn't know which function was going to be called! It couldn't know, because ultimately which actual method gets executed depends (down to the millisecond) on when the use chooses to execute the program. The random number generator is seeded on the current time. Moreover, from one run to the next, without re-compiling, a different method gets executed. This is truly new. You have never before seen code where you couldn't tell exactly which function/method would execute at a given call site. In this example, it's only at run-time that we can determine which function to execute. BTW: The only sane way for this to work (and the way it does work) is for the virtual machine, when it comes to the site of the call u.toString(), to check what type of object u refers to and allow that to determine which version of toString() gets executed.

A call site like this is called polymorphic. The word means "many shapes" and it's trying to get at the idea that many different functions may result from a single call site. Another common term to refer to a call like this is as a dynamic function call. A function call for which the actual function to be executed can be determined at compile time is referred to as static, because it is "unchanging" from run-to-run of the program, whereas a call for which the question of which actual function to execute can only be determined as the program executes is called dynamic, because it changes from run-to-run or even from call-to-call within a single program execution.

Polymorphism

Suppose we have a class Class1 and classes Class2, Class3, ... Classk, that are derived from Class1, either directly or through a chain of extends's. Suppose further that the base class Class1 defines a method method(Type1,Type2,...,Typen), and that many of the derived classes override this method. If a variable var is declared as a reference to the base class Class1, then when the call

var.method(arg1,...,argn)
is made, the actual method that gets executed is based on the type of the object var currently points to. So, if var currently points to an object of class Classi, then the Classi version of the method is the one that actually gets called.

We are all instances of class Object

We can see a slightly more interesting example of a polymorphic function call if I let you in on a little secret: all classes are derived from a class called Object. Specifically, when you define a class without the "extends" keyword, that class implicitly "extends" Object. So the class Point above is an Object. Now you can look Object up in the Java API documentation, and you'll see that it has some methods. Most notably for us, it has the method "toString()". That means you can call .toString() for any object. So let's expand our example:

Ex3.java
import java.util.*;
public class Ex3 {
  public static void main(String[] args) {
    Random   rand = new Random(System.currentTimeMillis());
    Point    v    = new Point(3, 4);
    LabPoint w    = new LabPoint(5, 2, "A");
    String   x    = "I'm a string";
    Scanner  y    = new Scanner(System.in);

    Object u;
    int i = rand.nextInt(4);

    if( i == 0 )
      u = v;
    else if( i == 1 )
      u = w;
    else if( i == 2 )
      u = x;
    else
      u = y;
    System.out.println(u.toString());
  }
}
     Object
       |
       |       Note: This diagram shows our simple "class hierarchy",
     Point           i.e. that LabPoint extends Point, which in turn
       |             extends Object.  As we get more and more into
       |             using inheritance, the hierarchies become more
    LabPoint         complex and these diagrams become more important.
Fun note: the way println works is that it implicitly calls the toString() function on whatever argument you give it, so we could change the last line to

System.out.println(u);
... and have the exact same program!

Vocabulary break!

We need to make sure we're on the same page with our vocabulary so that we can talk meaningfully about what happens in programs. Some concepts have one name in Java, but a different name in the larger world of programming languages. I'll use the generally accepted terms here, but point out what the Java-esque equivalents would be.

Okay, Polymorphism is great ... so what do I do with it?

Hopefully you've got the idea that polymorphism is a really cool mechanism. But what should we do with it? It's a hammer, what's the nail? Well, polymorphism solves the last of the three "problems" that we had with Procedural Programming not fully separating interface from implementation: how to allow multiple implementations for the same interface.

Below is a non-trivial example of how this does great things for us.

First consider the following program, Ex5.java, which generates expense reports. It makes use of classes Expense and ReportGenerator, whose interfaces are shown below (here is the full source code).

Ex5.java

public class Ex5 {
  public static void main(String[] args) {
    Expense[] e = {
      new Expense("lodging"),
      new Expense("meals"),
      new Expense("incidentals")
    };
    ReportGenerator.create(e);
  }
}
Expense.java

public class Expense {
  public Expense(String descrip);
  public String getDescrip();
  public double getAmount();
  public void   setAmount(double a);

  // Get amount from user and set
  // amount appropriately.
  public void ask(Scanner sc);
}
ReportGenerator.java

public class ReportGenerator {
  // Query users for amounts of the
  // expense categories listed in
  // array e, and output expense report.
  public static void create(Expense[] e);
}

Here's an example run of this program, so you can see how it works.

~/$ java Ex5
Enter dollar amount for lodging (0 for none): 357.89
Enter dollar amount for meals (0 for none): 170.08
Enter dollar amount for incidentals (0 for none): 58.23

 $0357.89  lodging
 $0170.08  meals
 $0058.23  incidentals
---------
 $0586.20  total

Now imagine that we want to include mileage expenses, but we want the user to simply enter the mileage and the program to calculate the mileage expense based on a hard-coded rate. What we need is for expense to just work a little differently for mileage. It needs to ask for an amount of miles, and set the expense amount to the miles times the rate. How can we make some instances of Expense behave in this new and different way? Inheritance! We will create a MileageExpense class and override ask() to take on this different behavior. The magic of polymorphism is that when the ReportGenerator calls the ask() method for an instance of MileageExpense, we'll get the new version of ask().

Ex6.java

public class Ex6 {
  public static void main(String[] args) {
    Expense[] e = {
      new Expense("lodging"),
      new Expense("meals"),
      new Expense("incidentals"),
      new MileageExpense("mileage","miles",  0.31)
    };
    ReportGenerator.create(e);
  }
}
MileageExpense.java

import java.util.*;
public class MileageExpense extends Expense {
  double rate;
  String kind;
  public MileageExpense(String descrip,
                       String kind,
                       double rate) {
    super(descrip + " (at " + rate + " dollars per " + kind + ")");
    this.rate = rate;
    this.kind = kind;
  }

  public void ask(Scanner sc) {
    System.out.print("Enter amount of " + kind + ": ");
    setAmount(sc.nextDouble() * rate);
  }
}
Here's an example run of the new program.

~/$ java Ex6
Enter dollar amount for lodging (0 for none): 357.89
Enter dollar amount for meals (0 for none): 170.08
Enter dollar amount for incidentals (0 for none): 58.23
Enter amount of miles: 302

 $0357.89  lodging
 $0170.08  meals
 $0058.23  incidentals
 $0093.62  mileage (at 0.31 dollars per miles)
---------
 $0679.82  total
What you need to pay special attention to is how polymorphism made this work. The ReportGenerator class and its create() method were written and compiled before anyone thought about writing MileageExpense, and yet we can feed it an array that contains MileageExpense, and the system works perfectly. That's magic! Now, what does this have to do with our quest for perfect separation of interface and implementation? Well, the last thing we identified as being a limitation of procedural programming was its requirement that every interface (function prototype) have only one implementation. If you look at the "interface" for the Expense class, you'll see that Expense class and the MileageExpense class both provide implementations for that interface. Specifically, MileageExpense implements void ask(Scanner sc) differently than Expense. So, polymorphism gives us the last thing we wanted: multiple implementation of the same interface!

Here is an alternate example of polymorphism for anyone interested: Poly.zip.