CarrCh07v2

CarrCh07v2 - C H A P T E R 7 Iterators CONTENTS What Is an...

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 7 Iterators CONTENTS What Is an Iterator? A Basic Iterator Iterator Methods That Modify the ADT Implementing an Internal Iterator Implementing an Iterator as Its Own Class An External Iterator An Inner Class Iterator PREREQUISITES Chapter 4 Chapter 6 Appendix B Lists List Implementations That Link Data Exception Handling OBJECTIVES After studying this chapter, you should be able to G G G G Describe the concept of iterator Use an iterator to traverse or manipulate a list Implement in Java an internal iterator, an external iterator, and an inner class iterator for a list Describe the pros and cons of the three kinds of iterators just mentioned A n iterator is an object that traverses a collection of data. During the traversal, you can look at the data entries, modify them, add entries, and remove entries. This chapter talks about iterators and applies them to the ADT list. You can add iterator methods to the operations of the ADT list, or you can define an iterator as a separate class that interacts with the ADT list. This separate class can be external to the ADT list or hidden within its implementation. We will look at all of these approaches. 151 152 CHAPTER 7 Iterators This chapter creates an iterator that is easy to understand and is useful as well. The Java Class Library also contains two iterator interfaces, Iterator and ListIterator, that we will explore in the next chapter. What Is an Iterator? 7.1 How would you count the number of lines on this page? You could use your finger to point to each line as you counted it. Your finger would keep your place on the page. If you paused at a particular line, your finger would be on the current line and there would be a previous line and a next line. If you think of this page as a list of lines, you would be traversing the list as you counted the lines. An iterator is a program component that enables you to step through, or traverse, a collection of data such as a list, beginning with the first entry. During one complete traversal, or iteration, each data item is considered once. You control the progress of the iteration by repeatedly asking the iterator to advance to the next entry in the list. At any time during the iteration, you can ask the iterator to give you a reference to the current entry. You also can modify the list as you traverse it. You are familiar with iteration because you have written loops. For example, if nameList is a list of strings, we can write the following for loop to display the entire list: int listSize = nameList.getLength(); for (int position = 1; position <= listSize; position++) System.out.println(nameList.getEntry(position)); Here, position is a simple iterator. Using it, the loop iterates through the entries in the list. Instead of simply displaying each entry, we could do other things to or with it. 7.2 Notice that the previous loop is at the client level, since it uses the ADT operation getEntry to access the list. For an array-based implementation of the list, getEntry can retrieve the desired array element directly and quickly. But if a linked chain of nodes represents the list’s entries, getEntry must move from node to node until it locates the desired one. For example, to retrieve the nth entry in the list, getEntry would begin at the first node in the chain and then move to the second node, the third node, and so on until it reached the nth node. At the next repetition of the loop, getEntry would retrieve the n + 1st entry in the list by beginning again at the first node in the chain and stepping from node to node until it reached the n + 1st node. This wastes time. Iteration is such a common operation that we could include it as part of the ADT list. Doing so would enable a more efficient implementation than we are able to achieve at the client level. Notice that the operation display of the ADT list performs an iteration. But display only displays the list. What if we want to do something else with the list’s entries as we traverse them? We do not want to add another operation each time we think of another way to use an iteration. Yet we should encapsulate a list traversal. We need an iterator that steps through a collection of data at our command and retrieves or modifies the entries. The iterator should keep track of its progress; that is, it should know where it is in the collection and whether it has accessed each entry. Note: Iterators An iterator is a program component that steps through, or traverses, a collection of data. The iterator keeps track of its progress during the traversal, or iteration. It can tell you whether a next entry exists and if so, return a reference to it. During one cycle of the iteration, each data item is considered once. What Is an Iterator? 153 A Basic Iterator 7.3 Let’s specify some methods that are appropriate for an iterator by writing the following Java interface. Our methods can throw an exception, so we begin by importing the exception from the package java.util. import java.util.NoSuchElementException; public interface BasicIteratorInterface { /** 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 return */ public boolean hasCurrent(); /** Task: Advances the current position of the iteration by 1. * @return true if the iteration has another entry to return */ public boolean advance(); /** Task: Retrieves the current entry in the iteration. * @return a reference to the current entry in the iteration, * if one exists * @throws NoSuchElementException, if no current entry exists */ public Object getCurrent() throws NoSuchElementException; /** Task: Sets the iteration to begin with the first entry in * the collection. */ public void reset(); } // end BasicIteratorInterface This interface is of our own design. Java provides other iterator interfaces that we will examine in the next chapter. Using our interface should help you to understand iterators in general and Java’s iterators in particular. You will notice that our interface specifies that an exception be thrown when the iterator does not find an expected entry. We do this to be consistent with Java’s iterator interfaces. Because NoSuchElementException is a run-time exception, the throws clause in a method’s signature is optional. Additionally, you do not have to write try and catch blocks when you invoke the method. (See Appendix B if you need more information about exceptions.) 7.4 Example. Let’s demonstrate these methods with the ADT list. The simplest, but not the best, way to implement an iterator interface is to define the iterator methods within the class that implements the ADT in question. Such an iterator is called an internal iterator. In this case, we will define a class that implements the two interfaces ListInterface, introduced in Chapter 4, and BasicIteratorInterface. Let’s call the resulting class ListWithInternalIterator. Suppose that we want to use this class to create a list of names. We will use strings for the names, but we could also use instances of the class Name that Chapter 1 presented. The following Java statements create such a list: String jamie = "Jamie"; String joey = "Joey"; String rachel = "Rachel"; ListWithInternalIterator nameList = new ListWithInternalIterator(); nameList.add(jamie); nameList.add(joey); nameList.add(rachel); 154 CHAPTER 7 Iterators At this point, nameList contains the names Jamie Joey Rachel The following sequence of events demonstrates the iterator methods; this sequence is illustrated in Figure 7-1: G G G G G G G G Figure 7-1 nameList.hasCurrent() returns true because a current entry—the first one in the list—exists. nameList.getCurrent() returns the string Jamie. nameList.advance() advances the iteration to the next name in the list and returns true. nameList.getCurrent() returns the string Joey. nameList.advance() advances the iteration to the next name in the list and returns true. nameList.getCurrent() returns the string Rachel. nameList.advance() advances the iteration beyond the end of the list and returns false. nameList.hasCurrent() returns false. The effect of iterator methods on a list Jamie Joey Rachel Jamie Joey Rachel advance() returns true getCurrent() returns Joey Jamie Joey Rachel advance() returns true getCurrent() returns Rachel Jamie Joey Rachel 7.5 hasCurrent() returns true getCurrent() returns Jamie advance() returns false hasCurrent() returns false Example. We can use the iterator to display the entries in the list. The following Java statements display the list nameList that Segment 7.4 defined: nameList.reset(); while (nameList.hasCurrent()) { System.out.println(nameList.getCurrent()); nameList.advance(); } // end while What Is an Iterator? 155 Compare this code with the for loop given in Segment 7.1. Both loops are independent of the implementation of the list. But this loop potentially can take less time to execute than the for loop. Since this iterator keeps track of its position within the list, getCurrent can access and return the current entry in the iteration faster, in general, than getEntry can in the for loop. As we noted in Segment 7.2, when a linked chain of nodes implements a list, getEntry must move from node to node until it reaches the desired one. Question 1 Assume that nameList is an instance of ListWithInternalIterator and contains at least three entries. Write Java statements that a. b. Display the third entry. Display the even-numbered entries in the list. That is, display the second entry, the fourth entry, and so on. Iterator Methods That Modify the ADT 7.6 Some iterators let you add, remove, or replace entries during a traversal. We can define a more extensive interface that includes such methods. Our new interface extends the previous BasicIteratorInterface as follows. Although it is not necessary to repeat the declarations of the methods in BasicIteratorInterface, we do so for reference. import java.util.NoSuchElementException; public interface IteratorInterface extends BasicIteratorInterface { public boolean hasCurrent(); public boolean advance(); public Object getCurrent() throws NoSuchElementException; public void reset(); /** Task: Adds a new entry immediately after the current entry, * if one exists. * @param newEntry the object that is the new entry * @throws NoSuchElementException, if no current entry exists */ public void addAfterCurrent(Object newEntry) throws NoSuchElementException; /** Task: Removes the current entry, if one exists, and * advances the iteration to the next entry. * @throws NoSuchElementException, if no current entry exists */ public void removeCurrent() throws NoSuchElementException; /** Task: Replaces the current entry with a new entry. * @param newEntry the object that replaces the current entry * @throws NoSuchElementException, if no current entry exists */ public void replaceCurrent(Object newEntry) throws NoSuchElementException; } // end IteratorInterface Note: An iterator can add, remove, or replace entries during a traversal. 156 CHAPTER 7 Iterators 7.7 Example. Once again, assume that the list nameList contains the names Jamie Joey Rachel Figure 7-2 illustrates the following sequence of events: G G G G G G Figure 7-2 nameList.hasCurrent() returns true because a current entry—the first one in the list—exists. nameList.getCurrent() returns the string Jamie. nameList.advance() advances the iteration to the next name in the list and returns true. nameList.getCurrent() returns the string Joey. nameList.removeCurrent() removes Joey from the list. nameList.getCurrent() returns the string Rachel. The effect of iterator methods on a list Jamie Joey Rachel hasCurrent() returns true getCurrent() returns Jamie Jamie Joey Rachel advance() returns true getCurrent() returns Joey Jamie Rachel After removeCurrent(), getCurrent() returns Rachel Jamie Rachel Ross After addAfterCurrent("Ross"), getCurrent() returns Rachel Jamie Brittany Ross After replaceCurrent("Brittany"), getCurrent() returns Brittany Jamie Brittany Ross advance() returns true getCurrent() returns Ross Jamie Brittany Ross advance() returns false hasCurrent() returns false getCurrent() throws a NoSuchElementException Implementing an Internal Iterator G G G G G G G G G 7.8 157 nameList.addAfterCurrent("Ross") adds the string Ross after Rachel. nameList.getCurrent() returns the string Rachel. nameList.replaceCurrent("Brittany") replaces Rachel with Brittany. nameList.getCurrent() returns the string Brittany. nameList.advance() advances the iteration to the next name in the list and returns true. nameList.getCurrent() returns the string Ross. nameList.advance() advances the iteration beyond the end of the list and returns false. nameList.hasCurrent() returns false. nameList.getCurrent() throws a NoSuchElementException. The iterator methods addAfterCurrent and removeCurrent enable us to alter the list, with a predictable effect on the iteration. What happens if we alter the list by using the list operations add or remove? Subsequent calls to the iterator’s methods could be unpredictable. For example, if we know the position of the current entry and use remove instead of removeCurrent to delete it, further iteration might not be reliable. You should be careful when using add and remove during an iteration. Question 2 Beginning with the list nameList that contains the names Jamie, Joey, and Rachel, describe the effect of the following Java statements: nameList.reset(); String name = (String) nameList.getCurrent(); nameList.removeCurrent(); nameList.advance(); nameList.addAfterCurrent(name); System.out.println(nameList.getCurrent()); nameList.display(); Implementing an Internal Iterator 7.9 In this section we modify the linked implementation of the list given in Chapter 6 by including the methods that IteratorInterface specifies. In doing so, we provide our list with an internal iterator. The resulting class has the following form. The differences between this class and the class outlined in Segment 6.23 appear in color. import java.util.NoSuchElementException; public class LinkedListWithInternalIterator implements ListInterface, IteratorInterface { private Node firstNode; private int length; private Node currentNode; // current node in iteration private Node priorNode; // node before the current node public LinkedListWithInternalIterator() { clear(); } // end default constructor public final void clear() { firstNode = null; length = 0; 158 CHAPTER 7 Iterators currentNode = null; priorNode = null; } // end clear < Implementations of the remaining methods of the ADT list go here; you can see them in Chapter 6, beginning at Segment 6.24.> . . . < Implementations of the methods in IteratorInterface go here; you will see them soon. > . . . < Implementation of the private class Node (Segment 6.22) goes here. > . . . } // end LinkedListWithInternalIterator Just as you can use your finger to keep track of your place on this page, our iterator implementation uses a reference to keep track of the iterator’s position within the list entries. This reference, which we call currentNode, is an added data field of the class. As you will see, the removeCurrent method will need a reference to the node before the current one. This reference is the data field priorNode. The default constructor initializes the references to the first, current, and previous nodes to null by calling the method clear. 7.10 The method hasCurrent. The methods in IteratorInterface work with the current entry in the list as the iteration progresses. Each method begins by determining whether the current entry in the iteration exists, so we begin by implementing the method hasCurrent: public boolean hasCurrent() { return currentNode != null; } // end hasCurrent 7.11 The method getCurrent. The method getCurrent retrieves the current entry. If the list is empty or the iterator has already reached the end of the list, no current entry will exist. In that case, the method throws an exception. We have chosen to throw an exception because the iterators that Java specifies throw exceptions. For simplicity, we use an exception provided by Java, namely NoSuchElementException. public Object getCurrent() throws NoSuchElementException { if (hasCurrent()) return currentNode.data; // or return currentNode.getData() else throw new NoSuchElementException("getCurrent(): " + "no current entry"); } // end getCurrent The definition of the private class Node given in Segment 6.22 of Chapter 6 did not include set and get methods for the private data fields data and next, since we can access them directly by name. As we mentioned in Segment 6.42, however, it can be a good idea to include set and get methods when defining a private inner class. If we had done so, we could write currentNode.getData() instead of currentNode.data, for example. Although we will continue to use the class Node in both this chapter and the next without set and get methods, as it was defined in Segment 6.22, we will add set and get methods to the private classes we create from now on. Implementing an Internal Iterator 7.12 The method replaceCurrent. To replace the current entry, the method similar implementation: 159 replaceCurrent uses a public void replaceCurrent(Object newEntry) throws NoSuchElementException { if (hasCurrent()) currentNode.data = newEntry; else throw new NoSuchElementException("replaceCurrent(): " + "no current entry"); } // end replaceCurrent Question 3 Consider a list of strings whose first two entries are Jim and Judy, and an iterator that is at the beginning of the list. If the iterator calls the method replaceCurrent with the string Ben as its argument, what does a subsequent call to getCurrent return? 7.13 The method advance. Before we advance currentNode so that it references the next entry in the list, we must copy its value to priorNode. In this way, the method remove will have references to both the current entry and to the entry that precedes it. Recall that you need both references to remove a node from a linked chain. Advancing the current entry’s reference to the next entry is also straightforward. The only subtlety involves the return value: If currentNode references the last entry, advancing it makes currentNode null and the return value false. public boolean advance() { boolean result = false; if (hasCurrent()) { priorNode = currentNode; currentNode = currentNode.next; result = hasCurrent(); } return result; } // end advance Question 4 If currentNode references the last entry in a list, a call to hasCurrent would return true. What would a call to advance return? Why? 7.14 The method removeCurrent. Removing the current entry takes a bit more care. Recall from Segment 6.34 of Chapter 6 that removing a node from a chain of linked nodes considers two cases. If the node to be removed—that is, the current node—is not first in the chain, you need a reference to the node before the current node. We have maintained such a reference in priorNode. Notice that we can determine whether the current node is first because priorNode is null in that case. The following implementation uses these observations: public void removeCurrent() throws NoSuchElementException { if (hasCurrent()) { length--; if (priorNode == null) // current node is first in list { firstNode = currentNode.next; currentNode = firstNode; } 160 CHAPTER 7 Iterators else // current node is not first in list { priorNode.next = currentNode.next; currentNode = currentNode.next; } // end if } else // no current node throw new NoSuchElementException("removeCurrent(): " + "no current entry"); } // end removeCurrent Figures 7-3 and 7-4 illustrate the steps in this method. After the method removes the current node, currentNode references the node that was after the one just removed, if such a node exists. Thus, if there is an entry after the one removed, it becomes the current entry. Otherwise, when the removed node was last in the list, currentNode becomes null. Figure 7-3 Before and after removing the current entry when its node is first in the chain Before firstNode priorNode currentNode After firstNode priorNode Figure 7-4 currentNode Before and after removing the current entry when its node is not first in the chain Before priorNode currentNode After priorNode currentNode Implementing an Internal Iterator 161 Question 5 Describe how Figure 7-3 would appear if the list contained only one entry. Question 6 list. Describe how Figure 7-4 would appear if the current entry was last in the Question 7 If currentNode references the last entry in a list, which list entry does a call to remove? removeCurrent Question 8 Following the call to call to hasCurrent return? Why? 7.15 Figure 7-5 removeCurrent in Question 7, what will a subsequent The method addAfterCurrent. Finally, when a current entry exists, adding an entry after it requires one case, as Figure 7-5 illustrates. Note that this addition does not change the reference to the current node. What was the current entry before the addition is still the current entry afterward. Before and after adding an entry after the current entry Before currentNode newNode After currentNode newNode public void addAfterCurrent(Object newEntry) throws NoSuchElementException { if (hasCurrent()) { Node newNode = new Node(newEntry); newNode.next = currentNode.next; currentNode.next = newNode; length++; } else throw new NoSuchElementException("addAfterCurrent(): " + "no current entry"); } // end addAfterCurrent. 162 CHAPTER 7 Iterators Question 9 7.16 Describe how Figure 7-5 would appear if the current entry was last in the list. The method reset. The last iterator method is reset: public void reset() { currentNode = firstNode; priorNode = null; } // end reset We can use this method to set the iteration to the beginning of the list. Note: Internal iterators An internal iterator results when you implement the iterator methods within the class that implements the ADT. Thus, an internal iterator can access an ADT’s data fields directly without using the public methods of the ADT. However, you can have only one instance of such an iterator at a time. As you will see in the next section, this restriction is a disadvantage. Implementing an Iterator as Its Own Class 7.17 An internal iterator might be easy to understand and use, but only one iteration of a list can occur at any time. Why would you want more than one iteration to be in progress simultaneously? Imagine a printed list of names that are not distinct and are in no particular order. Using an internal iterator is like running one finger down that list to count the names. Now suppose that you want to count the number of times each name occurs in the list. You can use two fingers, as follows. With your left hand, use one finger to point to the first name in the list. With your right hand, use one finger to point to each of the names in the list, starting with the first one. As you traverse the list with your right hand, compare each name to the name that your left hand marks. In this way, you can count the number of times the first name occurs in the list. Now move your left-hand finger to the next name in the list and use your right hand to point to the beginning of the list. Repeat the previous process to count the number of times that the second name appears in the list. Try it with the names in Figure 7-6. (Since your left hand will encounter Jane three times, you will repeat the computation needlessly unless you are careful. We consider this detail a bit later.) Each of your two fingers can traverse the list independently of the other. They are like two independent iterators that traverse the same list. An internal iterator would not give you this capability. Instead you must implement the iterator methods as a class separate from the class that implements the ADT—such as a list—to which you will apply the iterator. The two classes must, of course, interact in some way. The iterator, then, is an object separate from the list, and you can have several such iterator objects in existence at the same time. The iterator class can be public and separate from the class that implements the ADT. An instance of such an iterator class is called an external iterator. The iterator class can also be a private inner class of the class that implements the ADT. An instance of this inner class is called an inner class iterator. You use both of these iterators in the same way. But as you will see, an inner class iterator usually can execute faster than an external iterator. Implementing an Iterator as Its Own Class Figure 7-6 163 Counting the number of times that Jane appears in a list of names Number of times Jane appears in list Brad Left hand Right hand as it advances through the list Jane Bob 0 1 1 Jane 2 Bette 2 Brad 2 Jane 3 Brenda 3 Jane occurs 3 times 7.18 Suppose that nameIterator is either an external iterator or an inner class iterator that is associated with the list of names shown in Figure 7-6. We could invoke the methods in IteratorInterface just as the internal iterator did in Segment 7.4, replacing nameList with nameIterator. Thus, for example, G G nameIterator.hasCurrent() nameIterator.getCurrent() returns true because a current entry exists. returns the string Brad. Let’s write some code that counts the occurrences of each name in the list in Figure 7-6. Let hand in the figure. Now we’ll define a second iterator, your right hand. For each name that your left hand marks, your right hand traverses the entire list to count the occurrences of that name. Thus, we have the following nested loops: nameIterator correspond to your left countingIterator, that corresponds to while (nameIterator.hasCurrent()) { String currentName = (String)nameIterator.getCurrent(); int nameCount = 0; countingIterator.reset(); while (countingIterator.hasCurrent()) { String nextName = (String)countingIterator.getCurrent(); if (currentName.equals(nextName)) nameCount++; countingIterator.advance(); } // end while 164 CHAPTER 7 Iterators System.out.println(currentName + " occurs " + nameCount + " times."); nameIterator.advance(); } // end while With the names given in Figure 7-6, these statements produce the following output: Brad occurs 2 times. Jane occurs 3 times. Bob occurs 1 times. Jane occurs 3 times. Bette occurs 1 times. Brad occurs 2 times. Jane occurs 3 times. Brenda occurs 1 times. As you can see, since nameIterator (your left hand) encounters Brad twice and Jane three times, the computation in the inner loop is repeated needlessly. For example, we compute that Brad occurs twice each time nameIterator encounters Brad. If we are allowed to destroy the list, we can remove the duplicate entries—and thereby prevent the repeated computations—by modifying the if statement as follows: if (currentName.equals(nextName)) { nameCount++; if (nameCount > 1) countingIterator.removeCurrent(); } // end if When nameCount exceeds 1, the current entry of countingIterator must be a name that the iterator has already encountered in the list. Thus, we remove that entry. The iteration continues with the next entry, which is the new current entry. Exercise 9 at the end of this chapter considers the case when we cannot destroy the list. An External Iterator 7.19 Suppose that the iterator class ExternalIterator implements the interface IteratorInterface. After creating a list nameList, we would create an instance of the iterator, as follows: ExternalIterator nameIterator = new ExternalIterator(nameList); This invocation of ExternalIterator’s constructor connects the iterator nameIterator to the list by making one of the class’s data fields reference nameList. The class has an integer as another data field that indicates the current position of the iteration within the list. Thus, the class ExternalIterator could begin as follows: nameList public class ExternalIterator implements IteratorInterface { private ListInterface list; // reference to list private int currentPosition; // iterator position public ExternalIterator(ListInterface aList) { Implementing an Iterator as Its Own Class 165 list = aList; currentPosition = 1; } // end constructor . . . } // end ExternalIterator In addition to connecting the iterator to the list in question, the constructor begins the iteration at the first entry in the list. 7.20 Figure 7-7 The class ExternalIterator has no special access to the private data fields of the class that implements the list. It is a client of the list and so can process the list only by using the list’s ADT operations. Figure 7-7 shows an external iterator with a reference to an ADT but with no knowledge of the ADT’s implementation. The implementations of the iterator methods will use methods specified in ListInterface. The resulting implementations are rather straightforward but take longer to execute, in general, than the corresponding implementations for an internal iterator. An external iterator with a reference to an ADT, an indicator of its position within the iteration, and no knowledge of the ADT’s implementation An external iterator An ADT list 3 currentPosition list Ken Sue Tom Jen Bob For example, to retrieve the current entry in the iteration, getCurrent uses the list’s getEntry method: public Object getCurrent() throws NoSuchElementException { if (hasCurrent()) return list.getEntry(currentPosition); else throw new NoSuchElementException("getCurrent(): " + "no current entry"); } // end getCurrent 166 CHAPTER 7 Iterators The list method getEntry must traverse the linked chain of nodes to locate the node that contains the current entry. In contrast, the implementation of getCurrent in Segment 7.11 simply returned currentNode.data. We leave as exercises the implementations of the other methods in IteratorInterface. These implementations are independent of the implementation of the list and are actually simpler than those in the previous internal iterator. In general, however, they take longer to execute. Note: External iterators An external iterator must access an ADT’s data by using the public methods of the ADT. Thus, the typical external iterator takes longer to perform its operations than do other kinds of iterators. On the other hand, the implementation of an external iterator is usually straightforward. Additionally, you can have several independent external iterators in existence at the same time for a given ADT. To provide an iterator for an existing implementation of an ADT that cannot be altered, you must use an external iterator. An Inner Class Iterator 7.21 The internal iterator is efficient because it has direct access to the list’s private data fields. But you can have only one such iteration in progress at a time. The external iterator as a public class allows multiple and distinct iterations to exist simultaneously. However, since the iterator can access the list’s data fields only indirectly via ADT operations, the iteration takes more time than one performed by an internal iterator. The solution is to define the separate iterator class as an inner class of the ADT. Because the iterator is a separate class, you can have multiple iterations in progress at the same time. Moreover, since the iterator is an inner class, it has direct access to the ADT’s data fields. For these reasons, an inner class iterator is usually preferable to an internal or external iterator. To achieve our goal, we start by rearranging the pieces of the class that contained an internal iterator. We began that implementation in Segment 7.9. We will take the implementations of the methods specified in IteratorInterface and place them into a new inner class, IteratorForLinkedList. The outer class LinkedListWithIterator will be much like the class LList of Chapter 6 (Segment 6.23). It needs another method, however, that the client can use to create an iterator. This method, getListIterator, has the following simple implementation: public IteratorInterface getListIterator() { return new IteratorForLinkedList(); } // end getListIterator We will show you how to use this method shortly. To accommodate this new method, we create the following new interface that extends Interface instead of changing it: List- public interface ListWithIteratorInterface extends ListInterface { public IteratorInterface getListIterator(); } // end ListWithIteratorInterface This interface has all the list methods of ListInterface and the new method getListIterator. Implementing an Iterator as Its Own Class 167 Because a class can implement more than one interface, we can define the class without using our new interface. Having the interface, however, enables us to declare an object of type ListWithIteratorInterface and know that the object will have the list methods and the method getListIterator. LinkedListWithIterator 7.22 Now we can outline our new classes: import java.util.NoSuchElementException; 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, beginning at Segment 6.23. > . . . public IteratorInterface getListIterator() { return new IteratorForLinkedList(); } // end getListIterator private class IteratorForLinkedList implements IteratorInterface { private Node currentNode; // current node in iteration private Node priorNode; // node before the current node public IteratorForLinkedList() { currentNode = firstNode; priorNode = null; } // end default constructor < Implementations of the methods in IteratorInterface go here. > . . . } // end IteratorForLinkedList < Implementation of the private class Node (Segment 6.22) goes here. > . . . } // end LinkedListWithIterator The implementations of the methods declared in IteratorInterface appear within the inner class IteratorForLinkedList. They are the same as the implementations in the class LinkedListWithInternalIterator, given in Segments 7.10 through 7.16. 7.23 Example: Using an iterator to display a list. Since we’ve defined an interface ListWithIteratorInterface that includes the method getListIterator and the methods of ListInterface, we can use it to create a new list: ListWithIteratorInterface myList = new LinkedListWithIterator(); Now if we add entries to this list, we can display them by using our iterator. We first create an iterator object by invoking the new list method getListIterator: IteratorInterface myIterator = myList.getListIterator(); 168 CHAPTER 7 Iterators We then write a loop that is similar to the one you saw in Segment 7.5. Here the iterator object instead of the list object invokes the iterator methods: while (myIterator.hasCurrent()) { System.out.println(myIterator.getCurrent()); myIterator.advance(); } // end while Figure 7-8 illustrates an inner class iterator such as myIterator. The iterator has direct access to the ADT’s underlying data structure—a linked chain, in this example. Thus, the iterator can quickly retrieve the current entry in the iteration. Figure 7-8 An inner class iterator with direct access to the linked chain that implements the ADT An inner class iterator priorNode Ken An ADT list currentNode Sue Tom Jen Bob Note: Inner class iterators An inner class iterator has direct access to an ADT’s data, so it is as efficient as an internal iterator with about the same implementation effort. The advantage is that you can have several iterator objects in existence at the same time. These iterators can traverse a list independently of one another. C HAPTER S UMMARY G An iterator is a program component that enables you to traverse a collection of data. During the traversal, you can look at the data entries, modify them, add entries, and remove entries. G An internal iterator results when you add the iterator’s operations to the class that implements the ADT. As such, it can efficiently access an ADT’s data. G An internal iterator allows only one iteration of a list at any time. G An external iterator is an object of a public class that implements the iterator methods. This class is separate from but interacts with the class that implements the ADT. An external iterator must access an ADT’s data by using the public methods of the ADT. Thus, the typical external iterator takes longer to perform its operations than other kinds of iterators. On the other hand, implementing an external iterator is usually straightforward. G An external iterator enables you to have several iterators that traverse an ADT’s data independently. Implementing an Iterator as Its Own Class 169 G G E XERCISES An inner class iterator is an instance of an inner class defined within the class that implements the ADT to be traversed. The inner class implements the iterator methods and has direct access to the ADT’s private data. Thus, its methods can execute as quickly as those of an internal iterator with about the same implementation effort. In addition, you can have several independent iterator objects in existence at the same time. Typically, an inner class iterator is preferable to other types of iterators. 1. Describe the main advantage of an internal iterator over an external iterator, the main advantage of an external iterator over an internal iterator, and how an inner class iterator has both advantages. 2. 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? IteratorInterface nameIterator = nameList.getListIterator(); System.out.println(nameIterator.getCurrent()); nameIterator.advance(); nameIterator.advance(); System.out.println(nameIterator.getCurrent()); nameIterator.replaceCurrent("Brittany"); nameIterator.advance(); nameIterator.removeCurrent(); System.out.println(nameIterator.getCurrent()); nameList.display(); 3. Repeat Exercise 2, but instead use the following statements: IteratorInterface nameIterator = nameList.getListIterator(); nameIterator.removeCurrent(); nameIterator.removeCurrent(); nameIterator.advance(); System.out.println(nameIterator.getCurrent()); nameIterator.advance(); nameIterator.replaceCurrent("Brittany"); nameList.display(); System.out.println(nameIterator.getCurrent()); nameIterator.advance(); System.out.println(nameIterator.getCurrent()); 4. Suppose that nameList is a list of at least one string and that nameIterator is defined as follows: IteratorInterface nameIterator = nameList.getListIterator(); Write Java statements that use nameIterator to display only the last string in the list. Do this in two ways, as follows: a. Use the method hasCurrent. b. Do not use the method hasCurrent. 170 CHAPTER 7 Iterators 5. Consider adding a method getNext to BasicInteratorInterface with the following specifications: /** Task: Retrieves the current entry in the iteration and then * advances the current position of the iteration by 1. * @return the current entry in the iteration, if one exists * @throws NoSuchElementException, if no current entry * exists */ public Object getNext() throws NoSuchElementException; Implement this method by using methods in BasicInteratorInterface. 6. Given a list of strings and an iterator nameIterator whose data type is IteratorInterface, write statements that remove all occurrences of the string CANCEL from the list. 7. Given a list of strings and an iterator nameIterator whose data type is IteratorInterface, write statements that add the string Bob after the first occurrence of the string Sam. 8. Given a list of strings and an iterator object nameIterator whose data type is IteratorInterface, write statements that remove any duplicates in the list. 9. Given a list of strings and an iterator object nameIterator whose data type is IteratorInterface, count the number of times each string occurs in a list of strings without altering the list. Assume that you have an implementation of the ADT set. A set is an ADT that rejects duplicate entries. It includes the following methods add and contains: /** Task: Adds an entry to the set if it is not already present. * @param newEntry an object to be added as a new entry * @return true if the entry was added */ public boolean add(Object newEntry); /** Task: Determines whether the set contains a given entry. * @param anEntry the object that is the desired entry */ * @return true if the set contains anEntry */ public boolean contains(Object anEntry); 10. Given a list of strings and an iterator nameIterator, write Java code that shows how calls to the method add can cause conflict or inconsistency for an iterator. Assume the implementation given in Segment 7.9 and illustrate your code with pictures similar to those of Figures 7-3 and 7-4. 11. When an iterator is at the last item in a list, a call to advance returns false. A subsequent call to hasCurrent would then also return false, and a call to getCurrent would throw an exception. Consider the following alternate approach. When the iterator is at the last item in a list, a call to advance moves the iterator to the first item in the list. Describe the major drawback to such an approach. 12. Consider adding a method addBeforeCurrent(Object newEntry) to the IteratorInterface of Segment 7.6. The method would be similar to addAfterCurrent except that the new entry is inserted immediately before the current entry if one exists. Show how this might be implemented in the internal iterator of Segment 7.9. Implementing an Iterator as Its Own Class P ROJECTS 171 1. Revise the class AList, which uses an array to implement the ADT list, as follows. Have the class implement the interface IteratorInterface as an inner class. The class AList is given in Segments 5.5 through 5.10 of Chapter 5. 2. Complete the implementation of the class ExternalIterator that was begun in Segment 7.19. 3. Write Java code that uses iterators to rearrange a list of strings so that they are alphabetical. For example, if you rearrange the list given in Segment 7.17, the resulting list should be Bette, Bob, Brad, Brad, Brenda, Jane, Jane, Jane. 4. Implement the internal iterator of Segment 7.9 along with the method addBeforeCurrent described in Exercise 12. Use this iterator to write code that reverses a list of strings, as follows: Repeatedly remove the first string from the list and add it before the current first string of a new list. ...
View Full Document

This note was uploaded on 04/29/2010 for the course CS 5503 taught by Professor Kaylor during the Spring '10 term at W. Alabama.

Ask a homework question - tutors are online