Chapter 3 of 18
03 — Iteration
Loops, StringBuilder, and refactoring to String.repeat.
Kata: Repeater.repeat(text, times) returns text repeated times times.
New ideas: a for loop; test-driven discovery that one test isn’t enough; refactoring away
your own code in favour of the standard library.
Step 1 — write just enough of a test
Resist the big design up front. Write the smallest test that fails:
package iteration;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
class RepeaterTest {
@Test
void repeatsACharacterFiveTimes() {
assertEquals("aaaaa", Repeater.repeat("a", 5));
}
}
Run it: RED, Repeater doesn’t exist.
Step 2 — minimum code
Cheat, but deliberately:
public static String repeat(String text, int times) {
return text + text + text + text + text;
}
GREEN — and completely fake. This is normal. Now let the loop be driven by more tests.
Step 3 — the test that forces a loop
You know times is a parameter. Write a failing case for a different repetition count:
@Test
void repeatsMoreThanOneCharacter() {
assertEquals("ababab", Repeater.repeat("ab", 3));
}
RED. Now you can’t hard-code five texts. Time for the loop — and while you’re here,
notice why the loop uses a StringBuilder rather than +:
public static String repeat(String text, int times) {
StringBuilder out = new StringBuilder();
for (int i = 0; i < times; i++) {
out.append(text);
}
return out.toString();
}
REFACTOR — know the standard library
Java 11 ships String.repeat. Your loop is that method. Learning to spot “my code already
exists in the JDK” is a real skill:
public static String repeat(String text, int times) {
return text.repeat(times);
}
GREEN, tested, shorter. The refactor step was not optional — it’s where you learn the language. The tests protected you while you replaced the implementation.
Java-specific notes
StringBuilder.append—Stringis immutable in Java;text + text + ...in a loop creates a new string every iteration (O(n²)).StringBuilderis the escape hatch.for (int i = 0; i < times; i++)is the classic loop. Everything else —while,for-each,IntStream.range— has its place; TDD will reveal why each exists in later chapters.- Edge cases are cheap once the loop exists: a
repeatsZeroTimestest fortimes = 0is already covered by the same loop. Add a test anyway — it documents the contract.
Run it
gradle test --tests "iteration.RepeaterTest"
Key takeaway: the second test forced the honest implementation. Don’t design in your head; write a slightly different requirement and let your code evolve under the tests.
Next: 04 — collections — lists and sums.