CarrCh08v1

CarrCh08v1 - C H A P T E R 8 Java’s Iterator Interfaces C...

Info iconThis preview shows page 1. Sign up to view the full content.

View Full Document Right Arrow Icon
This is the end of the preview. Sign up to access the rest of the document.

Unformatted text preview: C H A P T E R 8 Java’s Iterator Interfaces C ONTENTS The Interface Iterator Implementing the Interface Iterator A Linked Implementation An Array-Based Implementation The Interface ListIterator Using the Interface ListIterator An Array-Based Implementation of the Interface ListIterator The Inner Class Java Class Library: ArrayList and LinkedList Revisited P REREQUISITES Chapter Chapter Chapter Appendix 5 6 7 B List Implementations That Use Arrays List Implementations That Link Data Iterators Exception Handling O BJECTIVES After studying this chapter, you should be able to G G G G Use an iterator that implements the Java interface Iterator Implement the interface Iterator as an inner class Use an iterator that implements the Java interface ListIterator Implement the interface ListIterator as an inner class T he previous chapter showed you how to define and use an iterator to traverse a collection of data. During the traversal, you can look at the data entries, modify them, add entries, and remove entries. You saw several ways to implement an iterator, and you learned that using an inner class for the iterator is desirable. This approach allows 169 170 CHAPTER 8 Java’s Iterator Interfaces the iterator direct and efficient access to the ADT’s underlying data structure. It also enables you to have several independent iterators in existence simultaneously. In the previous chapter we implemented our own iterator interface. This chapter discusses and implements two iterator interfaces that Java provides, Iterator and ListIterator. The Interface Iterator 8.1 Figure 8-1 Although the iterator we implemented in the previous chapter is useful and easy to understand, you still should be familiar with the iterator interfaces that Java provides. The Java Class Library provides two interfaces for iterators—Iterator and ListIterator—in the package java.util. The interface Iterator specifies only three iterator methods—hasNext, next, and remove. These methods traverse a collection of data from its beginning. We still have a current entry in a list, and initially it is the first entry. The iterator marks the current entry much as your finger can point to an entry in a list or to a line on this page. The method hasNext determines whether a current entry exists and returns true or false accordingly. It behaves just as our method hasCurrent does in the previous chapter. (See Segment 7.10.) If a current entry exists, the method next returns a reference to the current entry and then advances the iterator’s marker to the next entry in the list, as Figure 8-1 illustrates. Thus, next combines the behaviors of our methods getCurrent and advance from BasicIteratorInterface. (See Segment 7.3.) Repeated calls to next traverse through the list. The method remove removes the entry that next just returned. When you implement the Iterator interface, you do not have to provide a remove operation—it is optional—but you do need to define a method remove, because it appears in the interface. Such a method would simply throw the exception UnsupportedOperationException if the client invokes it. Note that Iterator does not include a method that resets the iteration to the list’s beginning. The effect of a call to next on a list (a) Before next() Jamie Joey Rachel Monica Ross 8.2 (b) After next() returns Rachel Jamie Joey Rachel Monica Ross Current entry Current entry As you read through the specifications of the methods in the interface methods with those in our BasicIteratorInterface. Iterator, compare the package java.util; public interface Iterator { /** Task: Determines whether the iteration has completed its traversal * and gone beyond the last entry in the collection of data. The Interface Iterator 171 * @return true if the iteration has another entry to return */ boolean hasNext(); /** Task: Retrieves the current (next) entry in the collection * and advances the iteration by one position. * @return a reference to the current entry in the iteration, * if one exists * @throws NoSuchElementException if the iteration had reached the * end already, that is, if hasNext() is false */ Object next(); /** Task: Removes from the collection of data the last entry * that next() returned. A subsequent call to next() will * behave as it would have before the removal. * Precondition: next() has been called, and remove() has not * been called since then. The collection has not been * altered during the iteration except by calls to this * method. * @throws IllegalStateException if next() has not been called, * or if remove() was called already after the last * call to next(). * @throws UnsupportedOperationException if this iterator does * not permit a remove operation. */ void remove(); // Optional } // end Iterator All of the exceptions mentioned in this interface are run-time exceptions, and so they do not have to appear within a throws clause of the methods’ signatures. Also, you do not have to write try and catch blocks when you invoke these methods. 8.3 Example. Let’s look at an example of how these methods work with the ADT list. Assume that we have implemented the interface Iterator as an inner class of the class that implements the ADT list. Further, assume that the list class has the method getListIterator. If nameList is a list containing the following names: Jamie Joey Rachel and nameIterator is defined as Iterator nameIterator = nameList.getListIterator(); the following sequence of events demonstrates the iterator methods: returns true because a next entry exists. returns the string Jamie and advances the iteration. nameIterator.next() returns the string Joey and advances the iteration. nameIterator.remove() removes Joey from the list. nameIterator.next() returns the string Rachel and advances the iteration. nameIterator.hasNext() returns false because the iteration is beyond the end of the list. nameIterator.next() causes a NoSuchElementException . G nameIterator.hasNext() G nameIterator.next() G G G G G Figure 8-2 illustrates these events. 172 CHAPTER 8 Java’s Iterator Interfaces Figure 8-2 The effect of iterator methods on a list Jamie Joey Rachel hasNext() returns true Jamie Joey Rachel next() returns Jamie and advances the iteration Jamie Joey Rachel next() returns Joey and advances the iteration Jamie Rachel remove() removes Joey from the list Jamie Rachel Jamie Rachel next() returns Rachel and advances the iteration hasNext() returns false next() causes a NoSuchElementException Question 1 Assume that nameList is a list that contains at least three entries and that is defined as in Segment 8.3 If nameIterator is at the beginning of the list, write Java statements that display the list’s third entry. nameIterator Question 2 Given nameList and nameIterator as described in Question 1, write statements that display the even-numbered entries in the list. That is, display the second entry, the fourth entry, and so on. Question 3 Given nameList and nameIterator as described in Question 1, write statements that remove all entries from the list. 8.4 Example. Let’s consider two situations that cause the exception iteration is at the beginning of the list and we write nameIterator.hasNext(); nameIterator.remove(); IllegalStateException. If the Implementing the Interface Iterator an IllegalStateException occurs because next was not called before we called larly, if we write 173 remove. Simi- nameIterator.next(); nameIterator.remove(); nameIterator.remove(); the second remove causes an IllegalStateException because since the most recent call to next. remove had been called already Note: Java’s Iterator interface specifies three methods: hasNext, next, and remove. Implementing the Interface Iterator 8.5 In this section, we will implement the interface Iterator by adding an inner class to each of two implementations of the ADT list. First, we will use a linked implementation of the list but will provide only the iterator operations hasNext and next. Then we will use an array-based list and implement all three methods of Iterator. As we did in Segment 7.21 of Chapter 7, we will define another interface that the list class can implement. It includes the operations of the ADT list and the method getListIterator. In this case, the method returns an instance of Iterator instead of returning an instance of IteratorInterface, as the method in Chapter 7 did. import java.util.*; public interface ListWithIteratorInterface extends ListInterface { public Iterator getListIterator(); } // end ListWithIteratorInterface A Linked Implementation 8.6 In this implementation, we do not want our iterator to support the remove method. We begin as we did in Segment 7.22 with the class LinkedListWithIterator, but we revise ListWithIteratorInterface as in Segment 8.5. Additionally, the inner class IteratorForLinkedList implements Iterator instead of IteratorInterface and has a different implementation. import java.util.NoSuchElementException; import java.lang.UnsupportedOperationException; public class LinkedListWithIterator implements ListWithIteratorInterface { private Node firstNode; private int length; < Implementations of the constructor and methods of the ADT list go here; you can see them in Chapter 6. > . . . public Iterator getListIterator() { 174 CHAPTER 8 Java’s Iterator Interfaces return new IteratorForLinkedList(); } // end getListIterator private class IteratorForLinkedList implements Iterator { private Node currentNode; // current node in iteration public IteratorForLinkedList() { currentNode = firstNode; } // end default constructor < Implementations of methods in the interface Iterator go here. > . . . } // end IteratorForLinkedList < Implementation of the private class Node (Segment 6.22) goes here. > . . . } // end LinkedListWithIterator 8.7 The method hasNext. Now consider the implementations of the methods in the inner class IterThe method hasNext has the same implementation as hasCurrent did in Chapter 7 for our interface IteratorInterface: atorForLinkedList. public boolean hasNext() { return currentNode != null; } // end hasNext 8.8 The method next. The implementation of next is a bit more involved. If hasNext returns true, and then advance currentNode. But if hasNext returns false, the method next should throw an exception. next should return the entry at currentNode public Object next() throws NoSuchElementException { if (hasNext()) { Node returnNode = currentNode; // get current node currentNode = currentNode.next; // advance iteration return returnNode.data; // return data in current node } else throw new NoSuchElementException("Iteration has ended; " + "illegal call to next."); } // end next 8.9 The method remove. Even though we decided not to support a remove operation for this iterator, we must implement the method because it is declared in the interface Iterator. If the client invokes remove, the method simply throws the exception UnsupportedOperationException. public void remove() throws UnsupportedOperationException { throw new UnsupportedOperationException("remove() is not " + "supported by this iterator"); } // end remove Implementing the Interface Iterator 175 This exception is in the package java.lang and so is included automatically in every Java program. Thus, the second import statement at the beginning of the class LinkedListWithIterator in Segment 8.6 is unnecessary. Note: The remove method An iterator that does not allow the removal of items during a traversal is not unusual. In such cases, the remove method is implemented, but it throws an exception if invoked. An Array-Based Implementation 8.10 For the array-based implementation, our iterator will support the remove method. Let’s begin with an array-based implementation of the ADT list. The class has the same data fields and methods as the classes AList and DynamicArrayList given in Chapter 5. But since our new class implements the interface ListWithIteratorInterface, it also includes the method getListIterator. This method returns an instance of Iterator instead of returning an instance of our interface IteratorInterface, as it did in Chapter 7. Our class also contains the inner class IteratorForArrayList, which implements the interface Iterator. The class has the following form: import java.util.*; public class ArrayListWithIterator implements ListWithIteratorInterface { private Object entry; // array of list entries private int length; private static final int INITIAL_SIZE = 25; < Implementations of the constructor and methods of the ADT list go here; you can see them in Chapter 5. > . . . public Iterator getListIterator() { return new IteratorForArrayList(); } // end getListIterator private class IteratorForArrayList implements Iterator { < Implementations of methods in the interface Iterator go here. > . . . } // end IteratorForArrayList } // end ArrayListWithIterator We now examine the inner class in detail. 8.11 Beginning the inner class. Just as you can use your finger to keep track of your place on this page, our iterator implementation uses an index to keep track of the iterator’s position within the array of list entries. This index, which we call currentIndex, is a data field of the inner class IteratorForArrayList . The constructor initializes currentIndex to zero. What makes the implementation of Iterator a bit tricky is the requirement that the client call next before each call to remove. In contrast, our iterator in Chapter 7 allowed us to call removeCurrent repeatedly to remove several entries from the list. Therefore, the implementation here needs 176 CHAPTER 8 Java’s Iterator Interfaces an additional data field—a boolean flag—that enables the remove method to know whether was called. We name this data field wasNextCalled. Thus, the inner class begins as follows: next private class IteratorForArrayList implements Iterator { private int currentIndex; private boolean wasNextCalled; // needed by remove public IteratorForLinkedList() { currentIndex = 0; wasNextCalled = false; } // end default constructor . . . 8.12 The method hasNext. The implementation of hasNext is straightforward. The iterator can retrieve the next entry if currentIndex < length. Notice that this expression is false when the list is empty. public boolean hasNext() { return currentIndex < length; } // end hasNext 8.13 The method next. The implementation of the method next follows the same logic as the version given in Segment 8.8. If hasNext returns true, next returns entry[currentIndex], increments currentIndex , and sets the flag wasNextCalled to true. On the other had, if hasNext returns false, next throws an exception. public Object next() throws NoSuchElementException { if (hasNext()) { wasNextCalled = true; Object currentEntry = entry[currentIndex]; currentIndex++; return currentEntry; } else throw new NoSuchElementException("next() called after " + "iteration has reached end."); } // end next 8.14 The method remove. The iterator’s method remove removes from the list the entry that the most recent call to next returned. Contrast this with the ADT operation remove, which removes the entry at a specified position within the list. Remember that a call to the iterator’s method remove is valid only if next has been called since the last call to remove. The class’s data field wasNextCalled helps us to implement this aspect of the method. Notice that we must also prevent a subsequent call to remove that is not preceded by a call to next. We do that by throwing an IllegalStateException. In addition, a subsequent call to next must behave as it would have before the removal took place. Although the inner class has direct access to the array entry, removing an entry from the list involves shifting elements within the array. Since we have already developed that code for the list’s remove method, we will call it from the implementation of the iterator’s remove method. To do that, Implementing the Interface Iterator 177 we need the position number of the list entry to be removed, rather than its array index. Recall from Chapter 5 that the position number of an entry in a list begins at 1, so it is 1 larger than the corresponding array index. Figure 8-3 illustrates how to use currentIndex in this implementation. The figure shows the array of list entries and the index currentIndex just before the call to next, just after the call to next but before the call to remove, and just after the call to remove . Notice that next returns a reference to the current entry—Chris in the figure—in the iteration and then increments currentIndex (Figure 8-3b). The method remove must remove this entry from the list. Since currentIndex is now 1 larger than the index of Chris, it is the position number of the list entry that must be removed. After the entry is removed, the next entry—Deb in the figure—moves to the next lowernumbered position in the array. Thus, remove decrements currentIndex so that it remains the index of the next entry in the iteration (Figure 8-3c). Figure 8-3 The array of list entries and currentIndex (a) just before the call to next(); (b) just after the call to next() but before the call to remove(); (c) after the call to remove() (b) next() returns Chris (a) (c) remove() removes Chris Art Art Art Bart Bart Bart Chris Deb Chris currentIndex Deb Deb Elly currentIndex Elly The method remove IteratorForArrayList: currentIndex Elly has the following implementation within the inner class public void remove() throws IllegalStateException { if (wasNextCalled) { // currentIndex was incremented by the call to next, so it // is the position number of the entry to be removed ArrayListWithIterator.this.remove(currentIndex); currentIndex--; // index of next entry to be removed wasNextCalled = false; } else throw new IllegalStateException("next not called for " + "this invocation of remove"); } // end remove This method calls the list’s method remove. We precede the call with to distinguish between the two versions of remove. tor.this ArrayListWithItera- 178 CHAPTER 8 Java’s Iterator Interfaces An inner class can refer to its outer class’s data fields and methods by name alone, if it does not also use the same names for its own definitions. For example, the method next in Segment 8.13 referred to the array entry directly by name since no other entry exists. But we could have written ArrayListWithIterator.this.entry instead. Question 4 a. b. Consider the list and the calls to next and remove in Figure 8-3. What would a call to next return if it occurred after the call to remove in Figure 8-3c? What would a call to next return if it occurred after the call to next in Figure 8-3b? The Interface ListIterator 8.15 The Java Class Library provides a second interface for iterators—ListIterator—in the package java.util. This type of iterator enables you to traverse a list in either direction and to modify the list during the iteration. In addition to the three methods hasNext, next, and remove that the interface Iterator specifies, ListIterator contains methods that work with the entry before the current one as well as methods that change, add, or replace list entries during the traversal. This added capability makes the interface harder to understand and to implement. We begin by looking at the interface: package java.util; public interface ListIterator extends Iterator { /** Task: Determines whether the iteration has completed its traversal * and gone beyond the last entry in the collection of data. * @return true if the iteration has another entry to visit when * traversing the list forward; otherwise returns false */ boolean hasNext(); /** Task: Retrieves the current (next) entry in the collection * and advances the iteration by one position. * @return a reference to the current entry in the iteration, if * one exists * @throws NoSuchElementException if the iteration had reached the * end already, that is, if hasNext() is false */ Object next(); /** Task: Removes from the list the last entry that either next() * or previous() has returned. * Precondition: next() or previous() has been called, but the * iterator’s remove() or add() method has not been called * since then. That is, you can call remove only once per * call to next() or previous(). The list has not been altered * during the iteration except by calls to the iterator’s * remove() or add() method. * @throws IllegalStateException if next() or previous() has not * been called, or if remove() or add() has been called * already after the last call to next() or previous() * @throws UnsupportedOperationException if this iterator does not * permit a remove operation */ void remove(); // Optional The Interface ListIterator 179 // The previous three methods are in the interface Iterator; they are // duplicated here for reference and to show new behavior for remove. /** Task: Determines whether the iteration has completed its traversal * and gone before the first entry in the collection of data. * @return true if the iteration has another entry to visit when * traversing the list backward; otherwise returns false */ boolean hasPrevious(); /** Task: Retrieves the previous entry in the list and moves the * iteration back to this previous entry. * @return a reference to the entry before the iterator’s * current entry, if one exists * @throws NoSuchElementException if the iterator has no previous * entry */ Object previous(); /** Task: Gets the index of the current (next) entry. * @return the index of the list entry that a subsequent call to * next() would return. If next() would not return an entry * because the iterator is at the end of the list, returns * the size of the list. Note that the iterator numbers * the list entries from 0 instead of 1. */ int nextIndex(); /** Task: Gets the index of the entry before the current one. * @return the index of the list entry that a subsequent call to * previous() would return. If previous() would not return * an entry because the iterator is at the beginning of the * list, returns -1. Note that the iterator numbers the * list entries from 0 instead of 1. */ int previousIndex(); /** Task: Adds an entry to the list just before the entry, if any, * that next() would have returned before the addition. This * addition is just after the entry, if any, that previous() * would have returned. After the addition, a call to * previous() will return the new entry, but a call to next() * will behave as it would have before the addition. * Further, the addition increases by 1 the values that * nextIndex() and previousIndex() will return. * @param newEntry an object to be added to the list * @throws ClassCastException if the class of newEntry prevents the * addition to this list * @throws IllegalArgumentException if some other aspect of newEntry * prevents the addition to this list * @throws UnsupportedOperationException if this iterator does not * permit an add operation */ void add(Object newEntry); // Optional /** Task: Replaces the last entry in the list that either next() * or previous() has returned. * Precondition: next() or previous() has been called, but remove() * or add() has not been called since then. * @param newEntry an object that is the replacement entry 180 CHAPTER 8 Java’s Iterator Interfaces * @throws ClassCastException if the class of newEntry prevents the * addition to this list * @throws IllegalArgumentException if some other aspect of newEntry * prevents the addition to this list * @throws IllegalStateException if next() or previous() has not * been called, or if remove() or add() has been called * already after the last call to next() or previous() * @throws UnsupportedOperationException if this iterator does not * permit a set operation */ void set(Object newEntry); // Optional } // end ListIterator Notice that ListIterator extends Iterator. Thus, ListIterator would include the methods and remove from the interface Iterator, even if we did not write them explicitly. We have done so for your reference and to indicate remove’s additional behavior. All of the exceptions mentioned in this interface are run-time exceptions, so they do not have to appear within a throws clause of the methods’ signatures. In addition, you do not have to write try and catch blocks when you invoke these methods. hasNext, next, 8.16 8.17 Figure 8-4 The current entry. Both ListIterator and Iterator use the idea of a current entry in a list. Initially, the current entry is the first entry in the list. The iterators mark the current entry, much as your finger can point to an entry in a list or to a line on this page. Recall that the method hasNext determines whether a current entry exists. If one exists, next returns a reference to the current entry and advances the iterator’s marker to the next entry in the list, as Figure 8-1 illustrated. Repeated calls to next step through the list. So far, nothing is different from what you learned about the interface Iterator earlier in this chapter. The previous entry. ListIterator also provides access to the entry just before the current one— that is, to the previous entry. The method hasPrevious determines whether a previous entry exists. If so, the method previous returns a reference to the previous entry and moves the iterator’s marker back by one entry so that it “points” to the entry it just returned. Figure 8-4 shows the effect of previous on a list. Intermixing calls to previous and next enables you to move back and forth within the list. If you call next and then call previous, each method returns the same entry. Like next, previous throws an exception when called after it has completed its traversal of the list. The effect of a call to previous() on a list (a) Before previous() Jamie Joey Rachel Monica Ross 8.18 (b) After previous() returns Joey Jamie Joey Rachel Monica Ross Current entry Current entry The indices of the current and previous entries. As Figure 8-5 shows, the methods nextIndex and previousIndex each return the index of the entry that a subsequent call to next or previous, respectively, would return. Note that the iterator numbers the list’s entries beginning with 0, instead of 1 as the ADT list operations do. If a call to next would throw an exception because the iterator is at the end of the list, nextIndex returns the size of the list. Similarly, if a call to previous would throw an exception because the iterator is at the beginning of the list, previousIndex returns -1. The Interface ListIterator Figure 8-5 181 The indices returned by the methods nextIndex and previousIndex Current entry Jamie Joey Rachel Monica Ross previousIndex() returns the index of this entry nextIndex() returns the index of this entry Note: The interface ListIterator specifies nine methods, including the three methods that specifies. They are hasNext, next, remove, hasPrevious, previous, nextIndex, previousIndex , add , and set. Iterator Using the Interface ListIterator 8.19 Example: Traversals. Let’s look at an example of the methods that work with the current and previous entries and then use it to describe the remaining methods in the interface. We make the following assumptions: G G G The interface ListIterator is implemented as an inner class of the class that implements the ADT list. The method getListIterator is added to the ADT list. The list nameList contains the following names: Jamie Doug Jill G The iterator traverse is defined as follows: ListIterator traverse = nameList.getListIterator(); Since traverse is at the beginning of the list, the Java statements System.out.println("nextIndex System.out.println("hasNext System.out.println("previousIndex System.out.println("hasPrevious " " " " + + + + traverse.nextIndex()); traverse.hasNext()); traverse.previousIndex()); traverse.hasPrevious()); produce the output nextIndex 0 hasNext true previousIndex - 1 hasPrevious false If we then execute the statements System.out.println("next " + traverse.next()); System.out.println("nextIndex " + traverse.nextIndex()); System.out.println("hasNext " + traverse.hasNext()); 182 CHAPTER 8 Java’s Iterator Interfaces the output is next Jamie nextIndex 1 hasNext true Finally, the statements System.out.println("previousIndex System.out.println("hasPrevious System.out.println("previous System.out.println("nextIndex System.out.println("hasNext System.out.println("next " " " " " " + + + + + + traverse.previousIndex()); traverse.hasPrevious()); traverse.previous()); traverse.nextIndex()); traverse.hasNext()); traverse.next()); produce the output previousIndex 0 hasPrevious true previous Jamie nextIndex 0 hasNext true next Jamie Question 5 Suppose that traverse is an iterator as defined in Segment 8.19, but the contents of nameList are unknown. Write Java statements that display the names in nameList in reverse order, beginning at the end of the list. 8.20 Example: The method set. The method set replaces the entry that either next or previous just returned. At the end of the previous segment, next had just returned Jamie, so the current entry is Doug. Thus, traverse.set("Bob"); replaces Jamie with Bob. Since Jamie was the first entry in the list, the list now appears as Bob Doug Jill Note that this replacement operation does not affect the position of the iterator within the list. Thus, calls to nextIndex and previousIndex, for example, are not affected. In this case, since the current entry is Doug, nextIndex returns 1 and previousIndex returns 0. Also note that we can call set again; doing so here will replace Bob. Question 6 If the iteration’s current entry is Doug, write Java statements that replace Jill with Jennifer. 8.21 Example: The method add. The method add inserts an entry into the list just before the current entry. Thus, the insertion is made immediately before the entry, if any, that next would have returned before add was called and just after the entry, if any, that previous would have returned. Note that if the list is empty, add inserts a new entry as the only entry in the list. If the current entry is Doug, the statement traverse.add("Kerry"); The Interface ListIterator 183 adds Kerry to the list just before Doug—that is, at index 1 or, equivalently, at list position 2. After this addition, the list is as follows: Bob Kerry Doug Jill A call to next at this point returns Doug, since next would have returned Doug had we not called add. If, however, we call previous instead of next, the new entry Kerry will be returned. Furthermore, the addition increases by 1 the values that nextIndex and previousIndex will return. Thus, immediately after the addition, nextIndex will return 2 and previousIndex will return 1. Question 7 If the iteration’s current entry is Doug, write Java statements that add Miguel right after Doug. 8.22 Example: The method remove. The method remove is similar to remove in the interface Iterawhich you saw earlier in this chapter. But in the interface ListIterator, remove is affected by the method previous as well as by next. Thus, remove removes the list entry that the last call to either next or previous returned. If the list contains tor, Bob Kerry Doug Jill and the iterator traverse is at Doug, the statements traverse.previous(); traverse.remove(); remove Kerry from the list, since previous returns Kerry. The iterator’s current entry is still Doug. Notice that both set and remove will throw the exception IllegalStateException if neither next nor previous has been called, or if either remove or add has been called already since the last call to next or previous. As you will see in the next section, this behavior complicates the implementation somewhat. 8.23 The optional methods set, add, and remove. Finally, the methods set, add, and remove are optional in the sense that you can choose not to provide one or more of these operations. In that case, however, each such operation must have an implementation that throws the exception UnsupportedOperationException if the client invokes the operation. An iterator of type ListIterator that does not support set, add, and remove is still useful, since it enables you to traverse a list in both directions. The implementation of such an iterator, which we leave as an exercise, is much simpler than the complete implementation that we present in the next section. Note: An iterator of type ListIterator that does not support set, add, and remove is simpler to implement and enables you to traverse a list in both directions. 184 CHAPTER 8 Java’s Iterator Interfaces An Array-Based Implementation of the Interface ListIterator 8.24 As we did for the interface Iterator earlier in this chapter, we will implement the interface as an inner class of a class that uses an array to represent the ADT list. First, we define an interface for the list class that includes the operations of the ADT list and the method getListIterator. In this case, the method returns an instance of ListIterator instead of Iterator. ListIterator import java.util.*; public interface ListWithListIteratorInterface extends ListInterface { public ListIterator getListIterator(); } // end ListWithListIteratorInterface 8.25 The class that implements the ADT list. Chapter 5 presented several classes that implemented the ADT list by using an array. AList used a fixed-size array, and DynamicArrayList used dynamic expansion. Our new class has the data fields and public methods of either AList or DynamicArrayList and includes the method getListIterator. It also contains the inner class IteratorForArrayList that implements the interface ListIterator. The class has the following form: import java.util.*; public class ArrayListWithListIterator implements ListWithListIteratorInterface { private Object entry; // array of list entries private int length; // current number of entries in list private static final int INITIAL_SIZE = 25; < Implementations of the constructor and methods of the ADT list go here; you can see them in Chapter 5. > . . . public ListIterator getListIterator() { return new IteratorForArrayList(); } // end getListIterator private class IteratorForArrayList implements ListIterator { < Implementations of the methods in ListIterator go here. > . . . } // end IteratorForArrayList } // end ArrayListWithListIterator The Inner Class 8.26 The data fields. We begin implementing the inner class IteratorForArrayList by thinking about how the methods set and remove will throw the exception IllegalStateException. This aspect of the implementation can be a bit confusing. These methods throw an IllegalStateException if either or previous was not called or or add has been called already since the last call to next or previous G next G remove An Array-Based Implementation of the Interface ListIterator 185 We will need several boolean flags as data fields to record whether these methods have been called. As you will see later, only the following three flags are necessary: private boolean wasNextCalled; private boolean wasPreviousCalled; private boolean wasAddCalled; We will be able to deduce whether remove has been called by examining only these flags. In addition to these three data fields, we need a marker for the current position of the iterator. Thus, the inner class begins as follows: private class IteratorForArrayList implements ListIterator { private int currentIndex; private boolean wasNextCalled; private boolean wasPreviousCalled; private boolean wasAddCalled; public IteratorForArrayList() { currentIndex = 0; wasNextCalled = false; wasPreviousCalled = false; wasAddCalled = false; } // end default constructor . . . 8.27 The method hasNext. The method hasNext has the same implementation that it had earlier in Segment 8.12. Recall that it returns true if the iterator has not reached the end of the list. public boolean hasNext() { return currentIndex < length; } // end hasNext 8.28 The method next. The implementation of next is similar to the one given in Segment 8.13. Here, however, it has other boolean flags to set. When next is called, a prior call to previous will be irrelevant to remove and set. Thus, we set wasPreviousCalled to false. Also, the set method cares when add is called after next. For set to detect a subsequent call to add, we must make wasAddCalled false. public Object next() throws NoSuchElementException { if (hasNext()) { wasNextCalled = true; wasPreviousCalled = false; wasAddCalled = false; Object currentEntry = entry[currentIndex]; currentIndex++; return currentEntry; } else 186 CHAPTER 8 Java’s Iterator Interfaces throw new NoSuchElementException("next() called after iteration " + "has reached end."); } // end next 8.29 The methods hasPrevious and previous. The methods hasPrevious and previous have implementations that are analogous to those of hasNext and next, respectively. public boolean hasPrevious() { return (currentIndex > 0) && (currentIndex <= length); } // end hasPrevious public Object previous() { if (hasPrevious()) { wasPreviousCalled = true; wasNextCalled = false; wasAddCalled = false; currentIndex--; return entry[currentIndex]; } else throw new NoSuchElementException("previous() called after " + "iteration has reached beginning."); } // end previous 8.30 The methods nextIndex and previousIndex. The method nextIndex returns either the index of the current entry or the size of the list if the iteration has passed the end of the list. public int nextIndex() { int result; if (hasNext()) result = currentIndex; else result = length; return result; } // end nextIndex The method previousIndex returns either the index of the entry just before the current entry or -1 if the iteration is at the beginning of the list. public int previousIndex() { int result; An Array-Based Implementation of the Interface ListIterator 187 if (hasPrevious()) result = currentIndex - 1; else result = -1; return result; } // end previousIndex 8.31 The method add. The method add adds an entry to the list just before the iterator’s current entry, as Figure 8-6 illustrates. If currentIndex is the index of the current entry within the array, we use the list’s add method to add an entry at position currentIndex+1 within the list. Recall that entries after the new entry will be shifted and renumbered. Therefore, we need to increment currentIndex so that it will continue to indicate the iterator’s current entry. Thus, add has the following implementation: public void add(Object newEntry) { wasAddCalled = true; currentIndex++; ArrayListWithListIterator.this.add(currentIndex, newEntry); } // end add Figure 8-6 The array of list entries and currentIndex (a) just before the call to add; (b) just after the call to add (a) Before add is called (b) After add("Ben") is called Art Art Bart Bart Chris currentIndex Ben Deb Chris Elly Added entry Deb currentIndex Elly 8.32 The method remove. The logic for the remove method when a call to next precedes the call to is like the logic for the remove method in the interface Iterator, which you saw in Segment 8.14. Recall that Figure 8-3 illustrated the array of list entries and the index currentIndex before and after the calls to next and remove. Figure 8-7 provides a similar illustration when a remove 188 CHAPTER 8 Java’s Iterator Interfaces call to previous precedes the call to remove. Notice that previous returns a reference to the previous entry—Bart—in the iteration and decrements currentIndex (Figure 8-7b). The method remove must remove this entry from the list. Notice that currentIndex is now 1 smaller than the position number of the list entry that must be removed. After the entry Bart has been removed, the next entry—Chris—moves to the next lower-numbered position in the array. Thus, currentIndex remains the index of the next entry in the iteration and so is unchanged (Figure 8-7c). Figure 8-7 The array of list entries and currentIndex (a) just before the call to previous(); (b) just after the call to previous() but before the call to remove(); (c) after the call to remove() (a) (b) previous() returns Bart Art Art Bart Bart (c) remove() removes Bart Art currentIndex Chris Chris Deb Deb Deb Elly Elly currentIndex Elly Chris currentIndex Remember, remove must throw an exception if neither next nor previous has been called or if either remove or add has been called already since the last call to next or previous. We can test the fields wasNextCalled and wasPreviousCalled to determine whether either next or previous, respectively, has been called. And we can look at the flag wasAddCalled to determine whether add was called after next or previous. But what about remove? If, as its last action, remove sets both the field wasNextCalled and the field wasPreviousCalled to false, it can detect whether it is being called a second time by testing these fields. Here is a summary of the logic necessary to detect when remove should throw an exception: G G If the flags wasNextCalled and wasPreviousCalled are both false, next and previous have not been called, or remove has been called already since the call to next or previous. If the flag wasAddCalled is true, add has been called since the call to next or previous. Figure 8-8 shows calls to remove in various contexts and the state of the boolean flags. In Part a, remove causes an exception because it is not preceded by a call to either next or previous. In Parts b and c, the first call to remove is legal, but the second call causes an exception. Each of the remaining calls to remove causes an exception. An Array-Based Implementation of the Interface ListIterator Figure 8-8 Possible contexts in which the method remove() is called (a) traverse.remove(); (b) traverse.next(); traverse.remove(); traverse.remove(); (c) traverse.previous(); traverse.remove(); traverse.remove(); (d) traverse.next(); traverse.add(...); traverse.remove(); (e) traverse.previous(); traverse.add(...); traverse.remove(); wasNextCalled is false, wasPreviousCalled is false Causes an exception wasNextCalled is true, wasAddCalled is false wasNextCalled is false, wasPreviousCalled is false Causes an exception wasPreviousCalled is true, wasAddCalled is false wasPreviousCalled is false, wasNextCalled is false Causes an exception wasNextCalled is true, wasAddCalled is false wasAddCalled is true Causes an exception wasPreviousCalled is true, wasAddCalled is false wasAddCalled is true Causes an exception The following implementation of remove reflects this discussion: public void remove() throws IllegalStateException { if (wasNextCalled && !wasAddCalled) { // next() called, but add() not called; // currentIndex is 1 more than the index of the entry // returned by next(), so it is the position number of // the entry to be removed ArrayListWithListIterator.this.remove(currentIndex); currentIndex--; wasNextCalled = false; 189 190 CHAPTER 8 Java’s Iterator Interfaces wasPreviousCalled = false; } else if (wasPreviousCalled && !wasAddCalled) { // previous() called, but add() not called; // currentIndex is the index of the entry returned by // previous(), so is 1 less than the position number of // the entry to be removed ArrayListWithListIterator.this.remove(currentIndex+1); wasNextCalled = false; wasPreviousCalled = false; } else throw new IllegalStateException("next or previous not " + "called for this invocation of remove"); } // end remove 8.33 The method set. The method set replaces the current entry of the iteration. It uses currentIndex, as updated by either of the methods next or previous. Since the method next returns entry[currentIndex] and then increments currentIndex, the method set would replace the object in entry[currentIndex-1] after a call to next. Likewise, since previous returns entry[currentIndex-1] and then decrements currentIndex , the method set would replace entry[currentIndex] after a call to previous. The following implementation of set reflects these observations and uses the same logic that we used in remove to determine whether to throw IllegalStateException: public void set(Object newEntry) { if (wasNextCalled && !wasAddCalled) entry[currentIndex-1] = newEntry; else if (wasPreviousCalled && !wasAddCalled) entry[currentIndex] = newEntry; else throw new IllegalStateException("next() or previous() not " + "called for this invocation of set(), " + "OR remove() or add() was called after next() " + "or previous(), but before set()."); } // end set Note: Implementing the entire interface ListIterator is more complex than implementing the interface Iterator. However, the implementation is easier when the associated ADT has an array-based implementation rather than a linked implementation. Java Class Library: ArrayList and LinkedList Revisited 8.34 In a sense, this entire chapter has been about the Java Class Library, since the interfaces Iterator and ListIterator are components of it. In this last section, we want to focus on another feature of the classes ArrayList and LinkedList that we mentioned in Chapters 5 and 6, respectively. Each Java Class Library: ArrayList and LinkedList Revisited 191 of these classes contains methods that are analogous to our method getListIterator. In particular, both classes contain the following methods: public Iterator iterator(); public ListIterator listIterator(int index); The method iterator returns an iterator that adheres to the Iterator interface. The method listreturns an iterator that begins at the list element indicated by index, where zero indicates the first entry in the list. This iterator has the methods specified in the ListIterator interface. In addition, ArrayList has the method Iterator public ListIterator listIterator(); which has the same effect as listIterator(0). The interface Iterator specifies three methods: hasNext, next, and remove. An iterator that implements this interface need not provide a remove operation. Instead, the method remove would throw the exception UnsupportedOperationException. The interface ListIterator specifies nine methods, including the three methods that Iterator specifies. They are hasNext, next, remove, hasPrevious, previous, nextIndex, previousIndex, add, and set . The methods remove, add, and set are optional in the sense that they can throw the exception UnsupportedOperationException instead of affecting the list. G E XERCISES G G C HAPTER S UMMARY You can implement each of the interfaces Iterator and ListIterator as an inner class of the class that defines the collection to be iterated. This approach enables you to have several independent iterators that traverse an ADT. It also allows the iterator direct access to the underlying data structure, so its implementation can be efficient. 1. Suppose that nameList is a list that contains the following strings: Kyle, Cathy, Sam, Austin, Sara. What output is produced by the following sequence of statements? Iterator nameIterator = nameList.getListIterator(); System.out.println(nameIterator.next()); nameIterator.next(); System.out.println(nameIterator.next()); nameIterator.remove(); System.out.println(nameIterator.next()); nameList.display(); 2. Repeat Exercise 1, but instead use the following statements: Iterator nameIterator = nameList.getListIterator(); nameIterator.next(); nameIterator.remove(); nameIterator.next(); nameIterator.next(); nameIterator.remove(); System.out.println(nameIterator.next()); nameList.display(); System.out.println(nameIterator.next()); System.out.println(nameIterator.next()); 192 CHAPTER 8 Java’s Iterator Interfaces 3. Suppose that nameList is a list of at least one string and that nameIterator is defined as follows: Iterator nameIterator = nameList.getListIterator(); Write Java statements that use nameIterator to display only the last string in the list. 4. Given nameList and nameIterator as described in Exercise 3, write statements that use nameIterator to remove all the entries from the list. 5. Given a list of strings and an iterator nameIterator whose data type is Iterator, remove all occurrences of the string CANCEL from the list. 6. Given a list of strings and an iterator nameIterator whose data type is Iterator, write statements that remove any duplicates in the list. 7. Given a list of strings and an iterator nameIterator whose data type is Iterator, count the number of times each string occurs in the list, without altering the list. Assume that you have an implementation of the ADT set with the methods add and contains. The ADT set will reject duplicate entries. 8. Suppose that nameList is a list that contains the following strings: Kyle, Cathy, Sam, Austin, Sara. What output is produced by the following sequence of statements? ListIterator nameIterator = nameList.getListIterator(); System.out.println(nameIterator.next()); nameIterator.next(); nameIterator.next(); System.out.println(nameIterator.next()); nameIterator.set("Brittany"); nameIterator.previous(); nameIterator.remove(); System.out.println(nameIterator.next()); nameList.display(); 9. Repeat Exercise 8, but instead use the following statements: ListIterator nameIterator = nameList.getListIterator(); nameIterator.next(); nameIterator.remove(); nameIterator.next(); nameIterator.next(); nameIterator.previous(); nameIterator.remove(); System.out.println(nameIterator.next()); nameIterator.next(); nameIterator.set("Brittany"); System.out.println("Revised list:"); nameList.display(); System.out.println(nameIterator.previous()); System.out.println(nameIterator.next()); 10. Given a list of strings and an iterator nameIterator whose data type is ListIterator, write statements that add the string Bob after the first occurrence of the string Sam. Java Class Library: ArrayList and LinkedList Revisited 193 11. Implement the interface Iterator as an external iterator. 12. If you wanted to implement the interface ListIterator as an inner class iterator by using a linked implementation, what difficulties would you face? PROJECTS 1. Revise the class LinkedListWithIterator given in Segment 8.6 so that the inner class IteratorForLinkedList provides a remove operation. 2. Implement all of the methods in the interface ListIterator as an external iterator. 3. Implement the interface ListIterator as an inner class, but do not support the operations remove, add, and set. ...
View Full Document

Ask a homework question - tutors are online