|
Here's a sample run: $ java SimpleCalc 4 + 5 * 2 = 14.0 + 3 = 17.0 / 0 Attempted divide by zero! 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 a 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.
Aside: Notice that
the Loggable is never one we intend to be
instantiated. Instead, we expect people
to extend it to create classes that will actually
be instantiated. Moreover, we need those classes to
override getTimeInMillis() and
gertLogMessage(), because our "stub"
implementations are really meaningless. Situations in which
there is a base class for which one or more
methods must be overridden, is common, and instead of
leaving it up to programmers to remember that they have to
override, we prefer to have the compiler force them
to override. The way to do this is to define the method
as abstract like this:
public abstract long getLogTimeInMillis();With
abstract methods, there is no body, the
class can never be instantiated, and any derived
class must override the method. Note that if a
class has even one abstract method then the class
must be declared abstract as well.
| ➔ |
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.
Important! Here's interfaces vs. classes in a nutshell:
class vs. interface extends vs. implements abstract
vs.
instance methods are always abstract (so
actually using the keyword explicitly is unnecessary)extend one class (i.e. there
is always one unique super class) but may
implement any number of interfaces.new, it must provide implementations for
all abstract methods of
its super class, and all methods of any
interface it implements.
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.