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 wise 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 individual characters in a string.
-
Create a class called
IndexableString that implements
the Indexable interface. What should the
parameterized type be (what are the components that will be
returned by get)?
-
Add 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 in this case, 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 object 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,
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 (what are the components that will be
returned by get)?
-
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 class wrapper to simplify your two method
implementations.
-
Create stub methods for the interface 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 should be similar, but
there are a few special cases to watch out for. The essence of the
algorithm is this:
-
If the magnitude of the number is less than 10 and the index
is greater than 0, that's a problem.
-
If the index is 0 then you can just return the number mod 10.
-
If the index is greater than zero, then we need to "shift off"
the first "index minus one" digits by dividing by 10 "index"
times. (That's a loop.)
-
If we get a result that is less than 10 at any point before we
are done with the above loop, we have a problem.
-
Otherwise, when are are done we can take the shifted result
mod 10 for our answer.
-
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 main class called
TestIndexable.
-
In the
main method of your class, create an ArrayList of Indexable objects.
-
Add several of your indexable strings and integers to the
ArrayList.
-
Use Java's "enhanced" for loop to print the component at the second index of every item in the list.