Section 4.5 MyIterator Class (Design Recipe)
In previous sections, we used raw loops (
for or while) to traverse arrays. But what if we want an object that encapsulates iteration state and behavior, so other code can ask “Is there a next element?” or “Reset to the start?” without fiddling with array indices? In this section, we’ll apply the Design Recipe step by step to create such a class: MyIterator.
We’ll build this step by step using the Java features we’ve learned so far. By the end, you’ll see how systematically walking through the design process (clarifying the problem, enumerating test cases, writing a skeleton, etc.) yields a robust, well-tested solution. This systematic approach will serve you well as you tackle more complex programming challenges in the future.
Subsection 4.5.1 An Authentic Example: Building MyIterator With the Design Recipe
Subsubsection 4.5.1.1 Step 0: Understand & Restate the Problem
Problem Prompt (Paraphrased): “We want a Java class that can traverse an array of integers, returning one element at a time. Instead of writing a
for loop everywhere, the rest of our code should just ask: ’Is there a next element?’ and ’Give it to me if so.’ The class should track where we are in the array and avoid out-of-bounds errors.
We also want a ’reset’ capability, so we can start over at the first element. If the array is empty or we run out of elements, we want predictable behavior—no accidental exceptions. Later, we might integrate this concept with Java’s for–each loops, but for now we’ll just rely on standard features like arrays and simple classes.”
Key Details & Clarifications:
-
Data: An
intarray, which we’ll store internally asdata. -
State: An integer
currentIndexthat marks which element to return next. -
Behavior
-
hasNext()— checks if more elements remain. -
next()— retrieves the current element and advances the index. -
reset()— starts iteration over from the first element.
-
-
Edge Cases
-
If
datais empty,hasNext()returns false from the start. -
If
next()is called when no elements remain, we either throw an exception or handle it gracefully—our design choice. We’ll opt to throw aRuntimeException.
-
Subsubsection 4.5.1.2 Step 1: Data Definition
We identify which fields or data elements this class needs:
-
private int[] data;The integer array we’re iterating over. We do not modify it. -
private int currentIndex;The “bookmark.” Ranges from0todata.lengthinclusive. OncecurrentIndex == data.length, no more elements remain.
By bundling the array and the index in one place, we minimize off-by-one mistakes scattered across the code. Instead, iteration logic lives inside
MyIterator, so callers just call hasNext() / next().
Subsubsection 4.5.1.3 Step 2: Method Signatures & Purpose Statements
We’ll provide four key methods:
| Method | Details |
|---|---|
| Constructor |
public MyIterator(int[] arr)
|
| hasNext() |
public boolean hasNext()
|
| next() |
public int next()
Return the current element
data[currentIndex] and then increment currentIndex. If we’re at the end, we’ll throw an exception.
|
| reset() |
public void reset()
Move
currentIndex back to 0 so iteration restarts at the first element.
|
Any additional methods (e.g., “peek” at current index) can be added later. These four satisfy the basics of iteration.
Subsubsection 4.5.1.4 Step 3: Examples & Test Cases
Before coding, we plan out examples—especially edge cases. We’ll likely turn these examples into actual
main checks or JUnit tests.
Test Case Table:
| Property | Description |
|---|---|
| Test Case 1: Non-empty Array (Normal Usage) | |
| Data | {10, 20, 30} |
| Operation | Check hasNext() at start |
| Expected Outcome | Returns true since currentIndex = 0 < 3
|
| Notes | We confirm that if data has 3 items, iteration isn’t done yet. |
| Test Case 1 (continued): Iteration Through Array | |
| Data | {10, 20, 30} |
| Operation | Call next() 3 times |
| Expected Outcome | Returns 10, then 20, then 30 in sequence, and updates index each time |
| Notes | After 3 calls, currentIndex should be 3. No items remain. |
| Test Case 1 (continued): End of Iteration | |
| Data | {10, 20, 30} |
| Operation |
hasNext() after 3 calls to next |
| Expected Outcome |
false, because currentIndex == 3
|
| Notes | No more elements left to return. |
| Test Case 2: Reset Scenario | |
| Data | {10, 20, 30} |
| Operation | Iterate to end, then call reset()
|
| Expected Outcome | Calling hasNext() after reset ⟹ true again, index=0 |
| Notes | We can start from the beginning again as if new iteration. |
| Test Case 3: Empty Array | |
| Data | {} |
| Operation |
hasNext() from the start |
| Expected Outcome |
false immediately |
| Notes | No elements exist at all. Next calls to next() can throw or handle gracefully. |
| Test Case 4: Next() Beyond End | |
| Data | {10, 20, 30} |
| Operation | 4th call to next() |
| Expected Outcome | Throw RuntimeException
|
| Notes | We’ve chosen to fail fast with an exception if we call next() illegally. |
These scenarios ensure we cover typical, boundary, and error conditions. For instance, an empty array is a corner case some might forget if they only tested an array of size 3.
Subsubsection 4.5.1.5 Step 4: Skeleton / Method Template
We now outline the class structure, leaving placeholders for final logic. This helps us verify we haven’t forgotten any method or data field.
public class MyIterator {
private int[] data; // The array we're iterating over
private int currentIndex; // Current position in the array
public MyIterator(int[] arr) {
// Initialize data with arr
// Set currentIndex to 0
}
public boolean hasNext() {
// Check if currentIndex is less than data.length
// Return true if there are more elements, false otherwise
return false; // Placeholder return
}
public int next() {
// If currentIndex is out of bounds:
// Throw a RuntimeException
// Otherwise:
// Get the element at currentIndex
// Increment currentIndex
// Return the element
return 0; // Placeholder return
}
public void reset() {
// Reset currentIndex to 0
}
}
At a glance, we see each method that the test table references. Next, we fill in the logic and then validate with examples from Step 3.
Subsubsection 4.5.1.6 Step 5: Implementation & Testing
We now fill in real Java code. Because we haven’t learned about exceptions yet, we’ll keep things simpler by returning a sentinel value (like
-999) if next() is called with no elements remaining, rather than throwing an error. In more advanced Java, we might throw an exception instead, but for now let’s avoid that. The rest of the design steps remain the same.
Try pressing “Run”. You’ll see the output from
main in the console. Then click “Show Tests” to see if everything passes. If anything fails, revisit the logic or the design steps.
Notice how we print a small message in
next() whenever we return -999, so we can observe in the console that we’ve gone beyond the last element. This is only an interim solution until we learn about exceptions later.
Subsubsection 4.5.1.7 Step 6: Reflection & Possible Revisions
With all tests passing, we might be “done.” But design is an iterative process. Let’s imagine new or changing requirements—these might send us back to earlier steps:
-
What if we truly want an error if next() goes too far? We’ve returned
-999as a sentinel, but in real Java code, a thrown error can be more explicit. Once we learn about exceptions, we might adjust Step 0’s problem statement (“We throw an error if out-of-bounds”), update Step 3’s test (“Expect an error, not a sentinel”), and re-implement Step 5’snext(). -
What if we want a generic type (not just int)? We’d need to revise Step 0 (“We want any type, not just ints!”), Step 1’s data definition, and Step 2’s method signatures to use generics. We’re not ready for that yet, but that’s how you’d do it.
-
What if arr can be null? Then Step 0 and Step 3 must define how to handle it—maybe
MyIteratorprints an error or reassignsarrto an empty array. That’s an example of how real designs expand over time when new edge cases appear.
In short, each design step is open to “walkbacks” if new info arises. Because we explicitly wrote out our data definitions, method specs, and test scenarios, it’s easy to see what to revise. This clarity is why the Design Recipe is so powerful.
With
MyIterator in hand, we’ve proven that iteration logic can live in an object that manages state (currentIndex) and behavior (hasNext, next). We no longer have to manually maintain a loop variable everywhere. In the upcoming chapters, we’ll see how Java’s Iterator and Iterable interfaces give you the for–each loop for free once you adopt their patterns. For now, enjoy our simpler, sentinel-based version—and keep the door open for refactoring once you learn about exceptions and generics!
Subsection 4.5.2 Summary
By applying the Design Recipe, we built a
MyIterator class that can “walk through” an integer array. We separated:
-
Data: The array itself (
data) plus an index (currentIndex). -
Behavior:
hasNext(),next(), andreset(). -
Edge-Case Handling: We tested empty arrays, normal usage, and a scenario where iteration goes beyond the last element.
This systematic approach ensures robust code that others (or you, in a few weeks!) can use without re-implementing the same iteration logic. The next step is to see how we can adapt this into Java’s standard
Iterable framework and for–each loops once we’ve learned about interfaces and inheritance. That’s coming soon!
You have attempted of activities on this page.
