An iterator is an object that can be used to navigate through a collection, inspecting each element of the collection. For example, if we wish to display every element of a set of strings, we can write the following:
void showSet(Set<String> s) {
Iterator<String> it = s.iterator();
while(it.hasNext()) {
String x = it.next();
System.out.println(x);
}
}
We are more used to writing the following more convenient version:
void showSet(Set<String> s) {
for(String x : s) {
System.out.println(x);
}
}
but this is simply shorthand for the version that uses an iterator.
In this question, you will provide iterator facilities for your StringStack
interface and implementing classes StringStackArray
and StringStackList
of question 1486.
Create an interface, StringStackIterator
, that offers the following methods:
public boolean hasNext();
public String next();
Add a new method to the StringStack
interface:
public StringStackIterator iterator();
The idea is that, given an instance of type StringStack
, it should be
possible to retrieve a StringStackIterator
from the StringStack
,
and then use this iterator to visit each element of the stack, from the top down.
You should now find that StringStackArray
and StringStackList
no
longer compile. This is because they do not implement the iterator
method.
In order to fix this, you need to create two new classes: StringStackArrayIterator
,
and StringStackListIterator
. Both classes should implement the StringStackIterator
interface. Writing these classes is a little tricky, but once you have them, implementing
the iterator
method in StringStackArray
is straightforward: simply
return a new StringStackArrayIterator
; implementing iterator
in
StringStackList
is similarly straightforward.
So, how should you implement the iterator classes? There are two choices:
-
Create a fresh class for each of
StringStackArrayIterator
andStringStackListIterator
. Let us consider theStringStackArrayIterator
case.StringStackArrayIterator
should have a field of typeString[]
: a reference to the contents of the stack. In addition, this class should have a field of typeint
that refers to the stack element that the iterator is currently pointing to. This should be initialised to the top of the stack. This solution is OK, but it requires the internal details of aStringStackArray
to be indirectly exposed to the separateStringStackArrayIterator
class, since aStringStackArrayIterator
is constructed using the internals of aStringStackArray
. More importantly, this setup allows clients to construct instances ofStringStackArrayIterator
independently of any actualStringStackArray
. This doesn't really make sense. The extent of this problem can be limited by making theStringStackArrayIterator
only package visible. -
Use inner classes. Again, let us discuss the "array" case; the "list" case is similar. You can create a class called
StringStackArrayIterator
insideStringStackArray
. This class can be declared private, meaning that onlyStringStackArray
is aware of its existence. (Recall that standard classes cannot be declared private -- this would make such a class completely useless. However, inner classes can be private, visible only to their enclosing classes; there is a compelling case for this, as the iterator example illustrates.) Only an object of typeStringStackArray
can create aStringStackArrayIterator
. Every instance ofStringStackArrayIterator
implicitly holds a reference to theStringStackArray
object that created it, and has direct access to (even private) fields of this object. This direct access allows you to implement the iterator methods without violating encapsulation. Try it!
If you are very confident with Java, try the inner classes approach. If you not so confident, try the first approach using standard classes. Ideally, try both so that you understand the differences.
Once you have implemented your iterators, implement toString
in each of StringStackArray
and
StringStackIterator
, so that the contents of the stack is represented as a string, with elements separated by commas.
You can use your StringStackIterator
interface to iterate through the stack in order to do this.
You should find that toString
looks exactly the same in both classes!
As a result, we can raise the implementation of toString
to a more abstract level. Create an abstract
class, AbstractStringStack
which implements the StringStack
interface. Now change StringStackArray
and StringStackList
so that instead of implementing StringStack
they extend
AbstractStringStack
. Now move the duplicate implementations of toString
into a single implementation
of toString
in AbstractStringStack
.
Finally, write a Demo
class which constructs two stacks, displays them both, uses
transferStacks
from question 1486 (which you should be able to copy into your new
Demo
class unchanged) to transfer one stack to the other, then displays them both.
Do you appreciate the power of abstract classes and interfaces here? We are able to implement the algorithm
behind toString
completely independently of the particular stack representation that is being used.
Some software engineers argue that it is
wasteful to give names to very simple inner classes, such as iterators. Furthermore, if an inner class is
supposed to have a single point of creation, we may wish to enforce this. Java supports the definition of
anonymous classes. This is where a nameless class is specified, and an instance of this class is immediately
created. Because the class has no name, it cannot be instantiated from elsewhere. If you are really on top of things,
then search online or in books for a tutorial on creating anonymous classes in Java, and rewrite your StringStackArray
and StringStackList
classes so that the inner "iterator" classes are anonymous.