CS 4: Lecture 24
Wednesday, April 19, 2006
JAVA INTERFACES
===============
Suppose you write an implementation of the Nelder-Mead simplex algorithm, and
you want to be able to use it to optimize many different objective functions.
The simplex algorithm frequently needs to call a method that evaluates the
objective function at a new vertex. But if that method is coded in Java, how
can you change it in the middle of a running program?
We solve this problem with the following steps.
- Declare a _Java_interface_ for calling an objective function, containing one
or more method prototypes.
- Define a bunch of different classes that _implement_ the Java interface, each
having a _different_ method for computing an objective function, but all
having the _same_ prototype.
- The simplex algorithm takes in a parameter whose "type" is the interface.
The actual parameter passed in is an object of one of those classes. The
object tells the simplex algorithm which method to call to compute the
objective function.
A "Java interface" is declared using Java's "interface" keyword, which refers
to something quite different than the interfaces I discussed in Lecture 13,
even though it's closely related. Henceforth, when I say "interfaces" I mean
public fields, public method prototypes, and the behaviors of public methods.
When I say "Java interfaces" I mean Java's "interface" keyword.
Here's an example of an interface, which is declared somewhat like a class, in
a file named Optimizable.java.
public interface Optimizable {
public double objectiveFunction(double[] parameters);
}
Notice that there is no implementation of objectiveFunction()! It's not
allowed in a Java interface. However, any class that _implements_ Optimizable
must provide an implementation of objectiveFunction() with the same prototype.
Here's an example of a class that computes an objective function f(p) = |p|^2.
public class Paraboloid implements Optimizable {
public double objectiveFunction(double[] parameters) {
double sum = 0.0;
for (int i = 0; i < parameters.length; i++) {
sum += parameters[i] * parameters[i];
}
return sum;
}
}
You could write a hundred other classes that all implement Optimizable but have
different objective functions. Then you can tell the simplex algorithm what
objective function to use by passing it an object of the appropriate class.
public void nelderMead(Optimizable opt) {
double[] p;
...
value = opt.objectiveFunction(p);
...
}
The magic here is that the parameter "opt" can be an object of _any_ class that
implements Optimizable! It's not restricted to be of one particular class.
How does Java know which objectiveFunction() method to call?
Dynamic Method Lookup
---------------------
Take note of the following two definitions.
_Static_type_: The type of a variable.
_Dynamic_type_: The class of the object the variable references.
The static and dynamic types aren't always the same. We can declare a variable
whose static type is Optimizable, but whose dynamic type is Paraboloid.
Optimizable opt = new Paraboloid();
value = opt.objectiveFunction(p);
How does Java execute the last line above? Java follows the reference "opt"
and figures out the class of the actual object "opt" points at. Then, Java
looks up the right objectiveFunction() method for that particular class at
run-time. This is called _dynamic_method_lookup_.
Note that there is no such thing as an Optimizable object. If you try writing
"new Optimizable()", Java will give you a compile-time error.
Often, I will just say "type" for static type and "class" for dynamic type.
Java chooses methods based on an object's class, never on a variable's type.
INHERITANCE
===========
Suppose you have a class Vector that represents two-dimensional vectors.
public class Vector {
double x, y;
public double x() {
return x;
}
public double y() {
return y;
}
public double length() {
return Math.sqrt(x * x + y * y);
}
}
Suppose in _another_ class you have a method that computes the angle between
a vector and the x-axis.
public static double xAxisAngle(Vector v) {
return Math.acos(v.x() / v.length());
}
Now, suppose you decide you need a 3D vector class as well as a 2D class.
You'd like to be able to reuse some of the code for vectors. In particular,
some vector-manipulating algorithms like xAxisAngle() are independent of the
dimension of the vector. Sometimes you can reuse them without changing them at
all.
Java provides a mechanism called _inheritance_ by which one class can inherit
the properties of another, then add more. For example, here is a Vector3D
class that inherits all the properties of the Vector class. (It is declared in
the a file called Vector3D.java.)
public class Vector3D extends Vector {
double z;
public double z() {
return z;
}
public double length() {
return Math.sqrt(x * x + y * y + z * z);
}
}
Vector3D is a _subclass_ of Vector, and Vector is the _superclass_ of Vector3D.
Vector3D has three fields: x, y, and z. It inherits x and y from Vector.
Every Vector3D object has a field "x" and a field "y", even though they're not
explicitly declared in the Vector3D class. The field z is explicitly declared
in Vector3D.
Vector3D also inherits two methods from Vector: x() and y().
A subclass can modify or augment a superclass in at least three ways:
(1) It can declare new fields--like z.
(2) It can declare new methods--like z().
(3) It can _override_ old methods with new implementations--like length().
The most important thing to understand about inheritance is this.
+---------------------------------------------+
| Every Vector3D object is a Vector object!!! |
+---------------------------------------------+
This has subtle implications. The first implication is that you can assign a
Vector3D object to a variable of static type Vector.
Vector v = new Vector3D(); // Okay.
However, _not_ every Vector object is a Vector3D object! If a Vector object
represents a 2D vector, it's not a Vector3D. So the reverse assignment is not
allowed.
Vector3D v = new Vector(); // COMPILE-TIME ERROR!
Another implication is that if a method takes a parameter of type Vector, as
xAxisAngle does, you can pass it an object of type Vector3D.
Vector3D v = new Vector3D();
v.x = 3;
v.y = 3;
v.z = 3;
angle = xAxisAngle(v); // Okay.
In this case, not only does xAxisAngle takes in v; it computes the correct
result. The result is correct for two reasons. First, the formula for the
angle between v and the x-axis, theta = arccos(v.x / |v|), works for vectors of
any dimension. Second, the Vector3D class _overrides_ the length() method, so
that it computes |v| correctly for a 3D vector. There are two different
length() methods--one for Vector and one for Vector3D--and Java needs to know
which one to call. Java makes the decision by...
Dynamic Method Lookup
---------------------
When we invoke a method that is overridden in a subclass, Java calls the method
for the object's _dynamic_ type, regardless of the variable's static type.
Vector v = new Vector();
l = v.length(); // Calls Vector.length()
v = new Vector3D();
l = v.length(); // Calls Vector3D.length()
In the statement "v = new Vector3D()", the static type of v is Vector, and the
dynamic type of v is Vector3D. The dynamic type controls which length() method
is called.
Java doesn't care that "v" has type Vector, or that the line "l = v.length();"
is exactly the same both times. It calls a different method the second time,
because the variable v references a different object. That's dynamic method
lookup in action.
Why is dynamic method lookup interesting? Consider what happens when you pass
a "Vector3D" object to xAxisAngle(). That method calls "v.length()". Because
Java uses dynamic method lookup, it checks the object that v points at, and
discovers that it's a Vector3D object. So it calls Vector3D.length(), which
correctly computes the length of v. If Java had called Vector.length() as
usual, it would have ignored the z coordinate and computed the length wrong!
-------------------------------------------------------------------------------
| WHY DYNAMIC METHOD LOOKUP MATTERS (Worth reading and rereading) |
| |
| Suppose you've written a class that uses Vectors extensively. By changing |
| the constructors so that they create Vector3Ds instead of Vectors, many |
| of the methods in your class automatically work on Vector3Ds as well, |
| without change. |
-------------------------------------------------------------------------------