Thurs, Apr 6.
Iterators using
Inner Classes
We have seen above how private inner
classes can represent
sub-objects, but we saw that the users ended up not being able to
directly
access the sub-object. For
an iterator, we want
the using code to use the Iterator
object directly.
How to proceed?
Iterator
is an interface, and so
no objects of that exact type can be instantiated.
In all cases of “Iterator
objects”, we actually have an object that ISA Iterator,
via inheritance.
The trick is to have a private inner
class that implements Iterator,
but itself is of a class unknown to the using code.
See this on pg. 522--LocalIterator is a private
class, but MyContainer "iterator()" method returns LocalIterator as an
Iterator, public interface, so caller can use it as an Iterator.
This is the setup of the final simple
example of Weiss, on
pg. 522. You should
add remove() to the
implementation of LocalIterator
to implement the real JDK Iterator
interface. Apparently
Weiss’s package has a simpler Iterator
interface.
See Figure 15.9 for the simpler
version of the code of Figure 15.8. Figure 15.9
shows how easy it is to refer to the outer object’s private
fields (size, items) from the inner
class code.
Recall the idea that an inner class is like a bump on a log.:
here the log is a List, say, and the bump moves along it allowing
us to get to the various elements.
Note that we can have multiple Iterators going at the same time: Figure 15.7 illustrates this abstractly.
Example: find the most duplicated String from a Collection C of Strings
Algorithm
Initialize maxCount = 0, maxString = null
Get an iterator itr1 over C
itr1 finds element s
Get an iterator itr2 over C, count duplicates of s in C
if (count > maxCount) {
maxCount = count;
maxString = s;
}
<Picture of log-like list with two bumps>
Note this algorithm is O(N*N). Actually we can do better by
sorting the Strings (O(NlogN) and then scanning the array once.
We can see that the MyContainer example can give out multiple
iterators. Each contains its own "current" value. It is
convenient that Weiss's Iterator interface doesn't have "remove()",
because that's the hard thing to get right.
Recall that the Robot class knew where its arm objects were, but
MyContainer doesn't keep track of its Iterator objects. That's
typical of containers and their iterators.
The JDK Collection classes are very careful about how iterators work when the Collection changes during the iteration.
public class TestArrayList {
public static void main( String[] args ) {
ArrayList<String> arr = new ArrayList<String>();
arr.add("foo");
arr.add("bar");
arr.add("foo2");
Iterator<String> itr = arr.iterator();
String s = itr.next(); // or leave this out
arr.remove("foo2");
itr.next(); // throws
}
}
dbtest(11)% java TestArrayList
Exception in thread "main" java.util.ConcurrentModificationException
at java.util.AbstractList$Itr.checkForComodification(AbstractList.java:449)
at java.util.AbstractList$Itr.next(AbstractList.java:420)
at TestArrayList.main(TestArrayList.java:12)
Similarly if one iterator does a remove, the other will throw when it tries to move.
If you do the remove before creating the iterator, all will work.
So how does this work?
Answer: the ArrayList object is keeping track of edits to itself by an
edit count, and when an iterator is created, it stores the initial
modCount, and later compares it to its original one.
So the code on pg. 522 is too simplistic for a JDK class--surely
MyContainer has an "add" or "insert" method that modifies its
contents...
But the inner class part of this code is fine. Just need to keep track of edits, make iterator throw when appropriate.
JDK ArrayList Implementataion
Class hierarchy:
Object
AbstractCollection
AbstractList <--but Weiss's implementation doesn't have this level
ArrayList
The book looks at AbstractCollection first--some common code for many collections, or default code begging for overrides--
isEmpty--can always be done by size()
clear--can always be done by iteration + remove, but there might be a better way
add--shows what should happen if no real "add" operation is appropriate, otherwise override
contains, remove--full sequential search, note use of element equals
(override if possible to do better) We are of course using
the equals of the object's runtime class, assuming it's there.
Recall the trickiness of getting this right with inheritance.
toArray-- primitive one is straightforward, but parametrized one uses reflection
Note
that we need <AnyType> IS-A <OtherType> to allow the
element copy.
Ex. a1 = ArrayList of Circles
Shape[] a0 = new Shape[1]; // set up model of what we want
Shape[] shapes = a1.toArray(a0);
All we're getting out of all of this is type checking at compile time and avoidance of downcasts. We could put
Shape[] shapes = (Shape [])a1.toArray();
and the downcast will work at runtime because a1 has Circles in it.
toString--uses StringBuilder, the newer faster version of StringBuffer--read about it on pg. 528.
ArrayList itself: extends AbstractCollection<AnyType> implements
List<AnyType> a little simpler than JDK
has one array "theItems", size, modCount for iterators
size vs. array size: sometimes the array won't be all used.
clear--does override
get, set--see O(1)
Note that set doesn't increment modCount--no change to *data structure*, just value held in it.
contains, remove--both call helper findPos, which checks for matching nullness as well as .equals.
remove(idx) moves elements down, but doesn't shrink array itself.
ListIterator for ArrayList
Iterators--recall that the current position is between two elements of
the list (positions 1, 2, ..., n-1), or before all elements (position
0), or after al (position n).
private inner class as expected
expectedModCount = initial modCount from outer object
What's this nextCompleted, prevCompleted for?
Go back to ListIterator contract, pg. 221:
remove: Removes the last item returned by next or previous. Can only be called once after next or previous.
That means we have to track next and previous and disallow sequences like
next, remove, remove
^removes element returned by next
^ it's gone!
whereas next, remove, previous, remove is OK
^ removes element e1 returned by next, closing up
list
^ steps across element that used to be before e1
^
removes element that used to be before e1
Thus each "next" or "previous" authorizes a possible remove and must be remembered in the iterator.
We can show that nextCompleted is true if and only if next has been
called and no other calls to previous or remove have since been
called., so it's OK to do a remove, which will remove the element
returned by that next call.
Similarly with prevCompleted.
So only one of nextCompleted and prevCompleted can be true, and it
shows which one (of the two around the current position) should be
removed when a remove comes in.
Look at code to prove the definitions and see the action in remove.