Summary: In this lab, you will explore both interfaces and Java generics by writing a generic interface and creating classes that implement it.
Preparation
-
Create a directory for this lab:
mkdir somewhere/generics
-
Go to your new directory:
cd somewhere/generics
Exercises
In this lab, you'll be creating an interface that allows one to
extract individual components from some linearly indexed object. This
is exactly analagous to the behavior of arrays. However, not all
objects are naturally stored as arrays. While we may want to provide
that kind of access, it may not be expedient to store them linearly.
Thus, you will create an interface that indicates this kind of access
and create some implementations that provide it on top of some type
that are not necessarily stored in a linearly indexed fashion.
Exercise 1
-
Create an interface called
Indexable that is
parameterized by a single type, let's call
it ComponentType.
-
Add two methods two your interface:
-
length, which returns an int
indicating the number of valid indices that may be accessed.
-
get, which takes an int for an index
(zero-based) and returns the object
of ComponentType at that index. The function
should throw an IndexOutOfBoundsException if the
index is greater than length()
Exercise 2
First we will declare a simple type of indexable object--something to
index and return individual characters in a string.
-
Create a class called
IndexableString that implements
the Indexable interface. What should the
parameterized type be (i.e., what type should be
returned by get for a string)?
-
Add empty stubs for the methods required by the interface, but do not
implement them yet (i.e., you can return
0
or null)
-
Check to see that your new class compiles. If you need help, be
sure to talk with a neighbor or the instructor.
-
Why might it be convenient if we could
say "
IndexableString IS-A String"?
-
Create your class so that this is true (i.e., create a subclass
relationship using
extends).
-
Now re-compile your code. What happens? Why?
-
It turns out that Java prevents us from creating an IS-A
relationship, so the best we can do is to use a HAS-A
relationship. Add a member and constructor so
that
IndexableString does indeed have
a String field to work with in its two required
functions.
-
Now, go ahead and implement the two functions required by the
interface. If you explore the
String object methods
in the API, it shouldn't be very hard!
Exercise 3
As it turns out strings are fairly naturally linearly indexed, since
that is essentially how they are stored in memory (though Java shields
us from this, unlike C). The individual (base 10) digits of integers
are not naturally stored this way, so it might be useful to provide an
interface for this in some applications.
Let's say that our convention will be that indexing starts with the
right-most digit (the "ones place"), and is zero
based. Thus,
get(2) on an
IndexableInteger
should return the digit in the "hundreds" place, or the third digit
from the right.
How should we handle the indexing of negative numbers? We'll let's say
that the length only includes the number of digits, and that indexing
always returns positive numbers.
-
Create a class called
IndexableInteger that
implements the Indexable interface. What should the
parameterized type be (i.e., what type should be
returned by get for an integer)?
-
Once again, we would really like the IS-A relation to be
true, but
Integer suffers from the same issue
as String. Use similar techniques for a HAS-A
relationship. Note that you may want to use the primitive type,
rather than the Integer class wrapper to simplify
your two method implementations.
-
Create empty stub methods for the interface methods as before.
-
How can we implement
length? Algorithmically,
speaking, this is the number of times we can divide the number by
10 before we get a number whose absolute value is less than 10. Use this
technique to implement the method.
-
Write a small test program to see if your length function works
correctly (you could just add a static
main to
the IndexableInteger class). Be sure to test on
negative numbers and numbers that are between -10 and 10.
-
The implementation of
get mathematically
straightforward, but there are a few special cases to watch out
for.
-
If the index equals or exceeds the length, that's
"exceptional" (i.e., a problem).
-
If the index is 0, you can find the last digit by returning
the number modulo 10.
-
Otherwise, you can "shift" off the rightmost digits by
dividing by 10index (using integer division). The
digit is the quotient modulo 10.
Hint: The
java.lang.Math class can help
you with exponentiation.
-
You'll need to watch out for and handle negative numbers correctly.
-
Test your implementation on several numbers.
Exercise 4
Now that you've had some practice with declaring interfaces and
implementing some classes that make use of generics (even though these
implementations are not generic at all!), now you can do some very
generic things. (But don't tell your parents that you came to college
to do generic things in class.)
-
Create a "driver" class called
TestIndexable.
-
In the
main method of your class, create
an ArrayList of Indexable objects.
-
Add several indexable string and integer objects to
the
ArrayList.
-
Use Java's "enhanced" for loop to print the component at the
second index of every item in the list.