A linear list is a generic way of storing Objects of any type. We might define a class to store generic linear lists:
class Linearlist { // Implement list as an array with arbitrary upper bound limit private int limit = 100; private Object[] data = new Object[limit]; private int size; // Current size of the list public Linearlist(){ // Constructor size = 0; } public void append(Object o){ data[size] = o; size++; if (size > data.length){ // Get more space! Object[] tmp = data; // Save a pointer to current array limit *= 2; data = new Object[limit]; // New array of double the size for (i = 0; i < tmp.length; i++){ data[i] = tmp[i]; } } ... }
Alternately, we might modify the internal implementation to use a linked list rather than an array.
class Node{ private Object data; private Node next; public Node(Object o){ data = o; next = null; } } class Linearlist { private Node head; // Implement list as a linked list private int size; public Linearlist(){ // Constructor size = 0; } public void append(Object o){ Node m; // Locate last node in list and append a new node containing o for (m = head; m.next != null; m = m.next){} Node n = new Node(o); m.next = n; size++; } ... }
Now, what if we want to externally run through a Linearlist from beginning to end? If we had access to the array data we could write
int i; for (i = 0; i < data.length; i++){ ... // do something with data[i] }
Alternatively, if we had access to head, we could write
Node m; for (m = head; m != null; m = m.next} ... // do something with m.data }
However, we have access to neither. In fact, we do not even know which of the two implementations is in use! The Linearlist class should add functionality to support such a list traversal. Abstractly we need a way to do the following:
Start at the beginning of the list; while (there is a next element){ get the next element; do something with it }
We can encapsulate this functionality in an interface called Iterator:
public interface Iterator{ public abstract boolean has_next(); public abstract Object get_next(); }
Now, we need to implement Iterator in Linearlist. If we make this a part of the main class, there will be a single ``pointer'' stored within the class corresponding to the temporary variable i (or m) in the loop we wrote earlier to traverse the list. This means that we will not be able to simulate a nested loop such as the following:
for (i = 0; i < data.length; i++){ for (j = 0; j < data.length; j++){ ... // do something with data[i] and data[j] } }
because when we execute the equivalent of the inner loop, we lose the position that we were in in the outer loop.
A better solution is to allow Linearlist to create, on the fly, a new object that implements Iterator and return it outside. This object will be an instance of a nested class, defined within Linearlist. It will have access to private information about the list, even though it is invoked outside! Here is how we do it.
class Linearlist { // Implement list as an array with arbitrary upper bound limit private int limit = 100; private Object[] data = new Object[limit]; private int size; // Current size of the list public Linearlist(){..} // Constructor public void append(Object o){...} // A private class to implement Iterator private class Iter implements Iterator{ private int position; public Iter(){ // Constructor position = 0; // When an Iterator is created, it points // to the beginning of the list } public boolean has_next(){ return (position < data.length - 1); } public Object get_next(){ Object o = data[position]; position++; return o; } } // Export a fresh iterator public Iterator get_iterator(){ Iter it = new Iter(); return it; } ... }
Now, we can traverse the list externally as follows:
Linearlist l = new Linearlist(); ... Object o; Iterator i = l.get_iterator() while (i.has_next()){ o = i.get_next(); ... // do something with o } ...
What if Linearlist is implemented as a linked list rather than an array? We just change the definition of the class Iter within Linearlist.
private class Iter implements Iterator{ private Node position; public Iter(){ // Constructor // Create a dummy node that points to the head of the list position = new Node(null); position.next = head; } public boolean has_next(){ return (position.next != null); } public Object get_next(){ position = position.next; Object o = position.data; return o; } }
Externally, the loop that we wrote with get_iterator()
,
has_next()
and get_next()
works as before.
Once again, we have passed a reference to an object but restricted access to the object using an interface. The Iter object created within Linearlist can (and must) access private variables within Linearlist, but the reference that is passed to this internal object is constrained by the Iterator interface, so this capability cannot be exploited externally.
In fact, we can go one step further and even nest the linked list class as a local class within Linearlist to prevent any accidental misuse. A complete implementation of Linearlist as a linked list might be as follows.
class Linearlist { private Node head; // Implement list as a linked list private int size; public Linearlist(){...} //Constructor public void append(Object o){...} private class Node{ private Object data; private Node next; public Node(Object o){ data = o; next = null; } } private class Iter implements Iterator{ private Node position; public Iter(){ // Constructor // Create a dummy node that points to the head of the list position = new Node(null); position.next = head; } public boolean has_next(){ return (position.next != null); } public Object get_next(){ position = position.next; Object o = position.data; return o; } } // Export a fresh iterator public Iterator get_iterator(){ return new Iter(); } ... }
Notice that when we return an iterator, since we do not need to ever use a name for the iterator locally, we can directly pass the reference generated by new, rather than assigning it temporarily to a locally defined reference (such as Iter it in the original version) and returning that local variable.