InterfaceIterator
The official topic list names one interface and no other: Implementing an interface, such as Comparable
. An iterator is not on that list. Work it once you are sure of Comparable, because writing one is the same skill on a harder promise, and because the practice exam includes one.
You write 2 short methods inside a nested class. Everything else in the file is already written. You check them by running the provided Tester and holding what your program prints against the expected output, line for line.
What the two methods must do
MyList keeps its values in an ArrayList<String>. It is declared implements Iterable, which is a promise that you can walk it with the enhanced for loop. That promise is only kept if something can hand out a cursor and step it one value at a time. The cursor is the nested class MyListIterator. It is written for you except for the two methods that make it move.
hasNextanswers one question. Is there still a value left to hand out? It returns true or false and it changes nothing, so calling it repeatedly gives the same answer every time.nexthands back the value the cursor is sitting on, and then moves the cursor forward one place, so the following call gives the value after it.
Both signatures are already in the file. Do not change them.
public boolean hasNext()
public Object next()
next returns Object, not String. The class is written against the raw Iterator type, so Object is the declared return. Leave it that way. Your compiler may print a warning about raw types. A warning is not an error and it costs you nothing.
What is already written for you
private ArrayList<String> list;onMyListholds the values.addLastputs each one on the end, so they come out in the order they went in.public Iterator iterator()returnsnew MyListIterator(0, this.list). A brand new cursor, starting at position 0, every single time it is called.- The nested
public class MyListIterator implements Iteratorhas two fields,private int curandprivate ArrayList<String> listRef, and a constructor that sets both from what it was passed. remove()already throwsUnsupportedOperationExceptionwith the messageremove not implemented for MyListIterator. It is not yours to write and not yours to change.toString()onMyListbuilds its result with an enhanced for loop over itself, so it depends on your two methods as well.
Follow these, and read the instructions you are handed on the day, because that is the only thing that says what breaks a problem.
- No helper methods. Each body is complete in itself. You do not get to add a private
advance. - Do not edit the driver. Here the driver is
Tester. Your two methods have to make its output correct without it changing at all. - Do not change any variable.
curandlistRefkeep their names, their types and their modifiers, and you do not add a field to the class. - No new imports. The file you are given already has
import java.util.*;at the top. Leave that line where it is and add nothing beside it.
And the one the official site puts plainly, on its Basic Strategies page: You may receive points for compiling code but you WON’T receive points for code that does not compile.
When you are running out of time, put the half-written method back to the stub you were given and make the project build.
The shape of the answer
hasNext is one comparison
The values sit at positions 0 through listRef.size() - 1. cur holds the position of the value that has not been handed out yet. So there is still something left exactly when cur is inside the list, and that is a comparison between cur and listRef.size(), strictly less than. Return the comparison. You can wrap it in an if that returns true or false, and it is not wrong, but it is more to get right under time pressure.
Two things hasNext must not do. It must not move cur, or the loop skips every other value. It must not ask listRef for a value in order to find out whether one is there, because asking for a position that does not exist is what hasNext exists to prevent.
next is three moves, in this order
- Read. Get the value at position
curout oflistRefand hold it in a local variable. - Move. Add one to
cur. - Return. Give back the value you held, not a fresh read after the move.
The order is the whole method. Move first and you hand back the wrong value and run off the end on the last call. A local variable inside your own method body is fine. The rule about not changing variables covers the fields and the signatures you were given, not a temporary you declare inside your own method.
Say the two sentences out loud before you write anything. hasNext looks and does not touch. next reads, then steps, then returns. If your code does not read like those two sentences, fix it before you run it.
Trace it once
The list holds apple, banana, cherry, so listRef.size() is 3 and a fresh cursor starts at cur 0.
| Call | cur before | Result | cur after |
|---|---|---|---|
hasNext | 0 | 0 < 3, true | 0 |
next | 0 | apple | 1 |
next | 1 | banana | 2 |
next | 2 | cherry | 3 |
hasNext | 3 | 3 < 3, false | 3 |
Read two things off that table. cur ends at size, not at size - 1, and that is correct. And hasNext is the only thing stopping the walk, which is why its comparison carries the safety of the whole method.
The enhanced for loop is that same walk
This line:
for (Object o : list)
System.out.println(o);
is compiled into this:
Iterator it = list.iterator();
while (it.hasNext())
System.out.println(it.next());
Nothing else happens. So the symptoms map straight back to your code. A for-each loop that prints nothing means hasNext returned false the first time it was asked. A for-each loop that never stops means next is not moving cur. A for-each loop that misses values means something other than next is moving cur.
Notice that the loop starts by calling iterator(), and iterator() builds a new MyListIterator at position 0. That is why the same list can be walked twice.
The cases, one at a time
Tester puts your 2 methods through 5 situations. Every one of them shows up in the output block, so work all 5.
1. Three values, walked with an explicit iterator
Tester gets an iterator into a variable named iter and runs a plain while (iter.hasNext()) loop. This is the straight case. It prints apple, then banana, then cherry. If this one is wrong, all of them are.
2. The same three values, with the enhanced for loop
The explicit walk just left its cursor at the end. The for-each loop still prints all three again, because it calls iterator() and gets its own cursor at 0. Nothing you write resets anything. If you were tempted to make next wrap back around to 0 when it runs out, this is the case that shows why you must not.
3. A list with one value
hasNext is true once, next returns single item, then hasNext is false because cur is 1 and the size is 1. This is the case that separates < from <=. With <= the loop asks for a second value, listRef.get(1) throws IndexOutOfBoundsException, and the program stops there, so everything printed after this point goes missing too.
4. An empty list, with the enhanced for loop
The size is 0 and cur is 0, so hasNext has to be false on the very first ask and the loop body must never run. In the expected output there is nothing at all under that heading. This is where 0 < 0 being false does the work for you, with no special case and no if for the empty list.
5. remove on the used-up iterator
At the end, Tester calls iter.remove() on the iterator from situation 1 and catches the exception. That method is already written and it throws whatever state the cursor is in, so this case comes out right as long as you have not touched it. Leave it alone.
Calling next when nothing is left. The Iterator contract in the Java API says throw NoSuchElementException. Tester never does this, so the printed output is the same either way. If you want to add the throw, NoSuchElementException is already available through the import that is in the file, so adding it breaks no rule. If you are short on time, skip it and get the 5 situations above right first.
Changing the list while a walk is running. Not tested here. Do not build for it.
The output you are being compared against
Run Tester and compare every line, blank lines included. Your editor may wrap the run in its own banner lines above and below; those are not part of the comparison.
Using an explicit iterator to walk through list
apple
banana
cherry
Using enhanced for loop to test Iterator
apple
banana
cherry
Testing on a single item list
single item
Testing on MyList object that is empty using enhanced for loop
Unsupported Operation Exception on call to remove.
remove not implemented for MyListIterator
Look at the gap after the empty list heading. That gap is the empty list printing nothing, followed by the blank line that opens the next message. If anything appears in that gap, your hasNext said true on an empty list.
Drill
Four questions. Work each one on paper before you tap.
Level-up check
1. Predict the output. A student writes this, then runs the empty list part of Tester.
public boolean hasNext()
{
return this.cur <= this.listRef.size();
}
The size is 0 and cur is 0, so 0 <= 0 is true and the loop goes in. next then calls get(0) on a list with nothing in it. One character is the difference between the whole output block matching and everything after this point going missing. The comparison is strictly less than.
2. Spot the bug. The three value list holds apple banana cherry. What does this next do?
public Object next()
{
this.cur++;
return this.listRef.get(this.cur);
}
On the first call cur goes 0 to 1 and it hands back banana, so apple is never printed. On the third call cur becomes 3 and get(3) is past the end, which throws. Read the value at cur first, hold it, then move cur, then return what you held.
3. Spot the bug. hasNext is correct. What happens when the enhanced for loop runs against this next?
public Object next()
{
return this.listRef.get(this.cur);
}
cur stays at 0 forever, so hasNext keeps saying 0 is less than 3 and next keeps handing back the same value. Moving the cursor is next's job and no other method does it for you. If your program floods the screen with one value, this is the line to look at.
4. Why twice? Tester walks the three value list with an explicit iterator until hasNext is false. The very next thing it does is a for-each loop over that same list, and the expected output shows all three values a second time. Why?
iterator() runs return new MyListIterator(0, this.list); every time it is called, so each walk gets its own cursor at its own position. The first one is still parked at the end, which is fine, because Tester only uses it once more to call remove. This is also the reason cur lives on the iterator and not on MyList. If the position belonged to the list, two loops over that list would fight over it.
Write it out
These two are not scored. Type them from memory into the box, then open the check and hold your version against it. The box is scratch space and is not saved.
One line. You have cur and listRef and nothing else.
- The comparison is between
curon one side andlistRef.size()on the other. - The operator is strictly less than. Not
<=, and notsize() - 1on the right. - Nothing changes.
curis the same after the call as it was before it. - You do not call
getanywhere in this method. - The return type stays
boolean, and returning the comparison itself is enough.
Then trace two lists through your version. An empty list gives 0 against 0, which has to come out false. A three value list at the end gives 3 against 3, which also has to come out false. If either one comes out true, the operator is the problem.
Three lines. Say the order out loud before you type it.
- The first thing that happens is a read of
listRefat positioncur, saved into a local variable of your own. - The second thing is
curmoving forward by exactly one. - The third is returning the value you saved, not another
getafter the move. - The declared return type stays
Object. Do not narrow it toString. - There is no bounds check and no
ifin here unless you chose to add theNoSuchElementExceptionthrow.
Then trace it. Three calls against apple banana cherry return those three values in that order and leave cur at 3. If your first call returns banana, you moved before you read.
Your checklist
Tick these when they are true.
Could you write this cold?
Not "did I follow along". Blank file, no notes, 4 hours on a clock, and no way to look anything up. Say yes only when that is true. You can take it back at any time.
If a piece of this is shaky
- An Interface Is a Promise a Type Makes
- Walking a List with a Cursor
- The Empty List Is Already Handled
- Declaring a Class Inside Another Class
- Implementing Comparable, the interface the official list names
- Data abstraction and class design
Read more, from the people who wrote it
Everything here was fetched and is the page it says it is.
- Comparable (Java SE 19 & JDK 19). The interface the official topic list actually names, and the one to be sure of first.
- List (Java SE 19 & JDK 19). What the class here is wrapping, and what a list already promises about position.
- Object (Java SE 19 & JDK 19). Why the declared return type of
nextisObjectand what you may do with one. - Interfaces (The Java Tutorials). What an interface is and what declaring one looks like.
- Implementing an Interface (The Java Tutorials). Signing the promise and keeping it, worked by Oracle.
- Interface (object-oriented programming) (Wikipedia). The general idea, separate from any one language.
- Lecture 6: Design, Debugging, Interfaces, MIT 6.092, on MIT OpenCourseWare. Interfaces as a design tool, in slides.