Pair Generic Lab
Categories:
6 minute read
Introduction
he goal of this lab is to explore key features of the Java collections framework. Collections are tailored to hold values of certain types through generic type parameters. Collection classes also provide iterators so that clients can loop through elements in a standard way.
Portions of this lab were developed by Dr. Michael Kirkpatrick with modifications by Dr. Chris Johnson.
Files
Download the following files:
Pair and PairTest
Your first task is to develop a generic `Pair`` class that represents a 2-tuple. A 2-tuple is a collection of two elements, a 3-tuple is a collection of three elements, and an n-tuple is a collection of n elements.
A Pair
abstraction is useful when you need to store ordered pairs like Cartesian coordinates, names and quantities, latitudes and longitudes, keys and values, and so on. To make Pair versatile, you don’t want to overconstrain the types of the two components. You want the first component to be of any type, and you want the second component to be of any type. The two components may have the same or different types.
The downloaded Pair
class is flexible: it can be used to store objects of any type. Unfortunately, it is both dangerous and inconvenient. It is dangerous because we may accidentally store an object of an unexpected type (as you saw above). It is inconvenient because we will usually need to cast objects retrieved from a Pair before they can be used.
We can address these issues by making our Pair class generic. Our improved class will accept two type parameters since we may want the type of the first element to be different from the type of the second. Complete the following steps. Follow these steps to build a generic Pair class and a set of unit tests:
-
Download files
-
Scan through
Pair.java
andPairTest.java
. Run the unit tests and confirm that they pass—for the moment. You are about to change both files so thatPair
uses generics instead of the meaninglessObject
class. -
Change
testPairCharacterInteger
so the Pair is declared with type parameters Character and Integer. In other words, you don’t want this raw instance:
Pair pair = new Pair('B', 12);
You want this parameterized instance:
Pair<Character, Integer> pair = new Pair<>('B', 12);
-
The parameters are optional in the second <> because the compiler infers them from the variable declaration. The tests will no longer compile. That’s to be expected. A principle of test-driven development is that you write the test first, and the code to be tested second.
-
Edit
Pair
to use generics and pass the first test. Note that all occurrences of Object should be replaced with a generic parameter. The type Object should not appear anywhere in the finished class. -
Eliminate the casts and value getters in the calls to assertEquals. Because of the generic parameter, the compiler knows the types and doesn’t need you to cast anything.
-
Implement
testPairDoubleDouble
andtestPairStringInteger
to perform appropriate tests for pairs of different component types. Make sure they pass. Note thatassertEquals
requires an extra tolerance parameter for doubles. -
Modify the
Pair
class to use generics so that it passes these three tests. Note that all uses ofObject
should be replaced with a generic parameter. NOTE: The type of Object should NOT appear anywhere in the finished class. -
Uncomment the remaining tests in
PairTest
and make sure they complete successfully.
Iterators
You have a versatile but typechecked Pair
class. Now create another collection, an array of Pair
.
Follow the instructions inside the PairList.java
file and complete the methods. The supporting files (PairListTest.java
and PairListDriver.java
) do not required any modification.
Why Not Generic Arrays
You cannot create an array of a generic type in Java. In the early days of the language, the Java designers decided that arrays could be subtypes of other array types, provided the elements followed a subtype relationship. Assigning an array of Integer`` to a reference of type
Object[]``, for example, is perfectly legal:
Object[] somethings = new Integer[10];
somethings[0] = 17;
But assigning to a reference of type String[]
is not because
Integer
is not a subtype of String
:
tring[] strings = new Integer[10]; // fails to compile
Assigning the array to a supertype has a cost.
The compiler can no longer detect that this code is dangerous because a
String
typechecks as an Object
:
somethings[0] = "Hydrogen";
So, the Java designers decided to add checks at runtime.
When an element is assigned, the Java virtual machine verifies
that the new element is a subtype of the array’s element type.
If not, it throws an ArrayStoreException
.
In this case, a String
is not an Integer, and we see the exception.
Now suppose you could make an array whose type depends on a generic parameter:
Object[] lists = new ArrayList<String>[10]; // only hypothetical
Because of the way Java implements generics, the generic parameter String
is only known at compile time. It is effectively erased in the compiled bytecode (this
is formally called type erasure). At runtime, then, the code behaves like this:
Object[] lists = new ArrayList[10]; // the generic is erased
The array’s element type is just plain ArrayList
.
Since the element type has been erased,
assigning a different sort of ArrayList
doesn’t generate an
ArrayStoreException
:
lists[0] = new ArrayList<Integer>(); // hypothetically legal
Being able to assign an ArrayList<Integer>
to an array of ArrayList<String>
would be bad news.
Programmers wouldn’t be able to trust Java’s typechecking. So, the designers forbid you from creating an array of elements whose type depends on a generic.
As a workaround, you may allocate a raw array but assign it to generic reference:
@SuppressWarnings("unchecked")
ArrayList<String>[] lists = new ArrayList[10];
The annotation is needed to disable the warning. Suppressing warnings is almost always a bad idea that you should avoid. Because the generics are present on lists
,
the compiler will catch any type mismatches at compile time.
Lets Use Generics
Take another look at the PairDriver.java file. Your IDE should now be showing multiple warnings indicating that you aren’t using the Pair class correctly: no type arguments are being provided.
Complete the following steps.
- Add type arguments so that the
Pair
instances all have String for the first object and Integer for the second. - STOP and THINK After adding the generics, can you remove the explicit casting in largestStadium? Why or why not? If you can, then go ahead and do so.
Lab Submission
Submit the following files together in Gradescope:
- Pair.java
- PairTest.java
- PairDriver.java
- PairList.java