We spent several lectures earlier in the quarter developing a class to represent a list of Strings using an array as the underlying data structure. The basic operations included the following:
We'd now like to explore reimplementing this class using a linked list instead of an array to store the data. This is a good example of data abstaction. From the client's view, the new implementation is exactly the same (presents the same interface) as the previous array-based one. But, although the new class behaves like the old one logically, the implementation is different and various operations will have different performance and behavior. One of the few external differences is that we will omit the constructor that specifies the list capacity, since we can always add nodes to a linked list (at least until we run out of memory).
For this example we'll use the following data structure for the links. This is almost the same as before, except it is specialized to string values, so we can avoid details of generic types, casting, etc.
public class StringLink { // a single node in a linked list of strings public String item; // string referenced by this link public StringLink next; // next node in the list or null if no next node // construct a new StringLink with given item and next fields public StringLink(String item, StringLink next) { this.item = item; this.next = next; } // construct a new StringLink with the given string and a null next reference public StringLink(String item) { this(item, null); } // construct a new StringLink without initializing any fields public StringLink() { this(null, null); } }
Now we want to look at implementing a LinkedStringList
. As before,
this is a class that contains both methods to implement the list operations
and state
to represent the list. For now, let's start with a single instance variable
that is either null
if the list is empty or else it refers to
the first link in the list.
/** A list of Strings (implemented with a linked list) */ public class LinkedStringList { // state private StringLink first; // reference to the first link in the list, // or null if the list is empty ... }
Now let's look at a couple of the operations. First the constructor. As with
all constructors, the job here is to make sure that any newly created LinkedStringList
is
properly initialized - in this case to a new, empty list. What should the code
be?
/** Construct a new, empty LinkedStringList */ public LinkedStringList() { }
Now, let's look at one of the basic operations, add(s)
, which
is supposed to add string s to the end of the list. There are some special
cases to consider, but let's ignore those for now and think about the "typical"
case: the list already contains some strings and we are adding a new one to
the end. First, draw a picture of what this looks like before the new string
is added, then sketch what needs to change to add it. (Make up some data -
anything you like)
Now, what do we need to do? We need to create a new link for the new
string, locate the last link in the existing list, and update that link's next
field
to point to the new node. Let's sketch out the code:
StringLink newLink = new StringLink(s, null); // advance to the end of the list StringLink p = ________________________; while ( _________________________________________ ) { } // add newLink to the end of the list
Now the edge cases: this works fine provided that the list is not empty. If
it is, then head is null
, and references to fields of the "first" node
in the list (head.next
or head.item
) will produce NullPointerExceptions
.
So we need to treat adding the first node to an empty list as a special case.
With all of that taken into account, we can write the code for add
.