Today's Challenge: can we have multiple implementations of one interface?
Recall that there are four basic mechanisms / ideas underlying object oriented programming:
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.
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;
}
}
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.
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.
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 AThis 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.
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 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:
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!
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.
double[] a = {1.3,1.6,1.8};
double sum = 0.0;
for( int i = 0; i < a.length; i++ )
sum += Math.sqrt(a[i]);
There is only one call site in this code, but when the code is executed, three function calls will result.
Foo x;
.
. ← x gets assigned to point to an object
.
x.func(arg1,arg2,...,argk);
Then to determine what actual function will get executed
you need to look not only at the types of arg1,...,argk in order to take function overloading
into account (here you'll be looking at their static types, e.g. the types of the references
rather than the objects they point to), but you also need to look at the actual, most
specific type of the object x points to — which may be of type Foo or may be of
a type derived from Foo.
this
pointer, that determines which
actual function gets called. So we say something like "the call is polymorphic on the
implicit argument". There are languages that are polymorphic on the other arguments,
but they are unusual.
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).
public class Ex5 {
public static void main(String[] args) {
Expense[] e = {
new Expense("lodging"),
new Expense("meals"),
new Expense("incidentals")
};
ReportGenerator.create(e);
}
}
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);
}
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().
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);
}
}
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);
}
}
~/$ 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 totalWhat 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.