Here's a sample run:
$ java SimpleCalc 4 + 5 * 2 = 14.0 c q
Now, completely separately, I have developed a system for "logging" things that happen in a program. This turns out to be a very important task, although normally for larger programs. In SI110 you may remember the importants of such logs for webserver programs. This is a super-junior system. It just logs the time (in milliseconds) for each event, and allows you to query log events by a range of times.
Forget about how the Logger methods work. What I want you to focus on is that we have an abstract class Loggable, and Objects that we want to be logged by this system are expected to extend the Loggable class. This is a common way to architect something like this.
This is an interesting question. Some languages (like C++)
allow a class to extend two classes simultaneously. This is
called multiple inheritance. Java does not. However
Java does allow a limited form of it. In Java, you can create
what's called an Interface, which is an abstract
class in which every method is pure abstrct (i.e. no
definitions), and while a class can only extend one base
class, it can extend any number of interfaces —
although the keyword is implements
rather than
extends
when it comes to interfaces.
Loggable
from a class to an interface
— after all, all the methods were pure abstract anyway.
Then we can define CalculatorException
as both extending
Exception and implementing the interface Loggable
.
This way, it will be able to fill both roles!
Finally, this gives us a SimpleCalc program that does everything we want in this example.
Here's a sample run:
~/$ java SimpleCalc 2 / 0 Attempted divide by zero! 1 + * Expected a number or c, got '*'! 3 = 5.0 x Expected *,+,= or c, got 'x'! q 6.154 Attempted divide by zero! 17.956 Expected a number or c, got '*'! 26.47 Expected *,+,= or c, got 'quit'!
Some people like to think of an interface as a contract. They think "if a class implements this interface, then I promise that the code that I've put together that relies on that interface will work". In your code, you can use an interface just as you do a base class: you define references to the interface as if it was a class type, and these references can point to any object whose actual type is a class that implments the interface.
As long as we can compare pairs of objects of a given type, we can sort them. So our starting point is an interface "Comparable" that captures this one operation:
public interface Comparable { public int compareTo(Object o); }In fact, this already exists int he Java API, so we don't need to declare it ourselves. Now I can write the selectionSort code with no assumptions about the types of the objects in the array except that they implement the Comparable interface:
The fact that only assume that the array elements implement Comperable is manifested in the code: the only method that gets called on an array element in this code is
compareTo()
.
When you declare a reference as type Comparable, that's all you
can do!
Now let's see a nice example of using this. We'll use our good
friend the Point class, and make it implement compareTo(),
where closeness to the origin defines the sorted order. We'll
also make use of the fact that the String class in Java
implements Comparable.
That's pretty cool! In the same program, we use MySort.sort() to sort Points and to sort Strings. Now we don't have to copy&paste every time we want to sort a new kind of object!
Iterable
.
Our Queue is basically already an example of an Iterable, we
merely need to state that explicitly (and add one little method
to Queue.Iter) in order to "implement" the interface.
The advantage to taking the time to make a class implement an
interface, is that you then get to use all the code that assumes
that interface — like making Point implement Comparable
allowed us to sort Points with MySort.sort(). In this case, one
advantage to implementing Iterable is that we can use Java's
"for-each loops". Assume you have bar
referring to
an object of a type that implements the Iterable interface,
where the objects you get from calls to "next()" have
type Foo
. Then you can iterate through all the
objects in the collection bar
with the following code:
for(Foo f : bar) { // do whatever with f }In the example below, we explicitly define Queue as implmenting Iterable<String> (don't worry about the meaning of the angle-brackets right now) and then give a simple driver that does a for-each loop to iterate through the elements of the Queue.