6.170 / Spring 2001 / Lab 4: Inheritance and Collections

Handout B4


Purpose

The purpose of this lab is to:

Setup

There are three exercises in this lab. To setup your environment to be able to work on them, at the athena prompt type:

mkdir ~/6.170/lab4
cp /mit/6.170/www/labs/lab4/* ~/6.170/lab4
cd ~/6.170/lab4

Overview of Inheritance

Inheritance is a very powerful design tool. It allows one to extend the capabilities of objects while reusing code that is already written. Some problems can arise, however, unless one follows the tenets of substitutability. Today we are interested in extending the capabilities of the Java SDK defined HashMap to be able to associate multiple values with a single key. We are going to do this in two ways, and both will implement MultiMap.

First, we will subclass HashMap to create MultiHashMap. To introduce the ability to store multiple elements while leaving the contracts for existing methods the same we will create two new methods, getAll and putAll, and override any methods necessary to implement the ability to map multiple values. Our method of doing this will be to let the superclass HashMap maintain a mapping from a key to a collection of values, rather than the actual values that are requested. MultiHashMap should not need to maintain any extra state beyond what is stored by its superclass. For this extension, get and put will have the same contracts as for HashMap. That is, put will return the previous value associated with a given key, or null if there was no mapping for key. And get will return the most recently associated value with the given key. Overriding get and put is enough to create a working HashMap that associates multiple values, however to make MultiHashMap implement MultiMap we will need to add two other methods, contains(Object key, Object value) and remove(Object key, Object value). While implementing this class, you might find java.util.LinkedList useful.

Now we have a working version of a HashMap that can store multiple values. However, without overriding the values, containsValue, and entrySet methods with very special care, our MultiHashMap cannot be substituted whenever a HashMap is needed. The problem is that HashMap promises that entrySet and values will return collections that should be backed by the HashMap (changes to the collections should change the HashMap), but we have changed the value that is stored to one the user is not expecting. The very nature of HashMap relies on the fact that one key can only be associated with one value. With a lot of work and care a subclass that stores multiple values could be made, and it could be substitutable for HashMap. However, mapping multiple values to a single key is unnatural to the purposes of HashMap, and any gains that would be made by subclassing HashMap would be lost by trying to make it both substitutable and functional. Since our MultiHashMap breaks the contract of substitutability, we will have to look for another solution.

An alternative to subclassing is to encapsulate HashMap into a CollectionMap. Encapsulation was done by having CollectionMap hold an instance of a HashMap, and redirecting calls to our class to the internally stored HashMap. This allows the added feature of "abstraction by parameterization" for which kind of Collection we use to store the multiple keys. The CollectionMap constructor has a parameter that will let that instance of CollectionMap know what kind of Collection it should use to store the multiple values. The encapsulating class CollectionMap is provided for you. You don't need to understand how the production of new Collections is done, but if you are curious how the creation of classes based on a prototype is done feel free to examine the code and experiment.

The next exercise involves writing a customized iterator to let users access all values stored in our CollectionMap. The specified methods that our iterator has to implement are: public Object next(); public boolean hasNext(); public void remove(). For this exercise, we do not plan to support the remove function (an optional operation as defined by the java.util.Iterator interface) and so it will suffice to throw an UnsupportedOperationException as its body. (Remember that even though we don't plan on supporting the remove method, the interface requires that our class have such a method defined.) The goal of this iterator is to return each and every value stored in our CollectionMap one element at a time. One approach to creating such an iterator would be to iterate through the entire map, iterating through each Collection of values.


Substitutability is a contract specifying that subclasses cannot require more, and cannot effect less. For example, imagine a Bike class, and RacingBike and LightedBike classes which are subclasses of Bike. Suppose the Bike class has a method called goHome() (which both LightedBike and RacingBike inherit) that requires that the sun has not yet set, and its effects are that it gets one home in exactly 10 minutes. In LightedBike, however, the goHome() method does not require that the sun has not yet set, since it has a light it will work perfectly well under this adverse condition. If it still promises to get home in 10 minutes, then LightedBike satisfies the contract of Substitutability because it requires less and promises the same effects. If RacingBike, however, requires that the sun has not yet set, and its effect is that it can perform the method in 5 minutes, then RacingBike breaks the contract of Substitutability. Even though some might consider it beneficial that the method takes 5 minutes less to perform, Bike specified that it would take exactly 10 minutes. If one were relying that the goHome() method would take 10 minutes and had been maliciously passed a RacingBike when he were expecting a Bike, his code would no longer behave predictably. Substitutability can also be summed up by saying that a subclass's specifications should be at least as strong as its superclass.

Encapsulation is the method of having one class contain an instance of another class that does all the actual work. The encapsulator (also known as a "wrapper" for the encapsulated class) acts as a filter between incoming calls and outgoing results. For instance, the object Integer is a wrapper for a primitive int. When the Integer method compareTo is called, it is the internal int's which are being compared.

Collection is an interface defined in Java version 1.2. It is the abstract idea of a group of Objects. As noted below in the Inheritance Tree (where interfaces are marked in italics, as well as lines on both the top and the bottom of the box, and classes are marked by normal text and a single line across the top of the box), List and Set are both sub-interfaces of Collection. List extends Collection by introducing the notion of an order to the group, while Set is the abstract notion of a set, a group of elements with no duplicates. All subclass relations are marked by a vertical connection, and all implementation relations are marked by a horizontal one.

An inheritance tree of Collections

The class java.util.Collections provides many useful static methods for use in dealing with Collections, Lists, and Sets. Some notable ones are: sort, binarySearch, min, and max.

Iterators are classes that allow the user to step through the elements contained by some type of collection. Iterator classes can be thought of as little parasites latching on to another class that contains elements, and obtaining each element sequentially (with each next call), then moving forward to the next one. They are so useful that the interface for Collection specifies that any class that wants to implement Collection must first have a method that returns an iterator that will step through each element. Closely related to Iterator is the java.util.Enumeration class. In fact, Iterator is just an update to Enumeration with the added (optional) remove operation, and shorter method names.