You run a small program that totals a cart of four items. It prints three lines, and the second one says the total is 36,500. The third gives an average price. The run exits normally, and not one of the three lines looks broken. Only the arithmetic disagrees with the receipt.
Key takeaways
i < prices.length - 1stops the loop one index early, so the last price never enters the sum.- The average still divides by
prices.length, so the total and the average are both wrong while the count line stays right. - The mirror bound
i <= prices.lengththrowsArrayIndexOutOfBoundsException: Index 4 out of bounds for length 4. for (int price : prices)walks the array with no index, so there is no bound to write one short, and it gives up the position.whiletests before the body and prints nothing on an empty array;do-whiletests after and runs the body once.- A checker whose expectations are fixed fails on correct code when the input changes, so it tests the input as much as the code.
The loop that stops at the third item
The file holds an int array of four prices, a counter, a sum, and an average.
// Total.java
public class Total {
public static void main(String[] args) {
int[] prices = {12000, 15000, 9500, 10500};
System.out.println("items: " + prices.length);
int total = 0;
for (int i = 0; i < prices.length - 1; i++) {
total += prices[i];
}
System.out.println("total: " + total);
System.out.println("average: " + total / prices.length);
}
}
Compile and run it with javac Total.java && java Total. The exit status is 0, with no warning and no error.
$ javac Total.java && java Total
items: 4
total: 36500
average: 9125
That is three lines, and all of them are plausible. The count says 4, the total is a believable number, and so is the average. But the four prices add to 47000, so the total is 10,500 short.
Why the last item never enters the loop
Look at the bound: prices.length - 1. The array holds four prices, so that is 3, and the condition i < 3 holds for i equal to 0, 1 and 2. Index 3 is never read. The last price, 10500, never reaches the sum.
The damage has a second half. The average still divides by prices.length, which is 4. So two lines are wrong, not one. The total is short by 10500, and that short total is then divided by four items when only three were added. The right average is 47000 / 4, which is 11750.
The mirror mistake keeps the same array and the same loop body, and changes only the bound. Write i <= prices.length instead. This version does not skip an item. It reads past the end, and the program stops with java.lang.ArrayIndexOutOfBoundsException: Index 4 out of bounds for length 4. The trace points at Total.main(Total.java:8), and the process exits with status 1.
One of these mistakes is silent and the other is loud. The silent one ships.
A check you can run
You can catch the quiet version with a second program. This checker reads the three lines the program prints and compares them against the values the four prices should produce.
// CheckTotal.java
import java.util.List;
import java.util.Scanner;
public class CheckTotal {
public static void main(String[] args) {
List<String> expected = List.of("items: 4", "total: 47000", "average: 11750");
Scanner in = new Scanner(System.in);
int passed = 0;
for (int i = 0; i < expected.size(); i++) {
String got = in.hasNextLine() ? in.nextLine() : "<nothing>";
if (got.equals(expected.get(i))) {
passed++;
System.out.println("PASS line " + (i + 1) + ": " + got);
} else {
System.out.println("FAIL line " + (i + 1) + ": expected " + expected.get(i) + ", got " + got);
}
}
if (in.hasNextLine()) {
System.out.println("FAIL extra line: " + in.nextLine());
}
System.out.println();
System.out.println(passed + " of " + expected.size() + " checks passed");
if (passed != expected.size()) {
System.exit(1);
}
}
}
Pipe the program into it with javac CheckTotal.java && java Total | java CheckTotal.
$ javac CheckTotal.java && java Total | java CheckTotal
PASS line 1: items: 4
FAIL line 2: expected total: 47000, got total: 36500
FAIL line 3: expected average: 11750, got average: 9125
1 of 3 checks passed
The line items: 4 passes. The total and the average do not, so the checker prints 1 of 3 checks passed and exits non-zero. The program alone said nothing, and the checker caught it at once.
Stop counting
The fix is to stop counting. Swap the counted loop for a for-each loop and change nothing else.
// Total.java
public class Total {
public static void main(String[] args) {
int[] prices = {12000, 15000, 9500, 10500};
System.out.println("items: " + prices.length);
int total = 0;
for (int price : prices) {
total += price;
}
System.out.println("total: " + total);
System.out.println("average: " + total / prices.length);
}
}
$ javac Total.java && java Total
items: 4
total: 47000
average: 11750
$ java Total | java CheckTotal
PASS line 1: items: 4
PASS line 2: total: 47000
PASS line 3: average: 11750
3 of 3 checks passed
The for-each loop hands you the value instead of the position. Against this file the checker prints 3 of 3 checks passed and exits 0. The loop visits every element exactly once, in order. There is no bound to write down, so there is nothing to get one short.
The trade is honest: you lose the index. If you need to know which position you are on, the counted for stays the right tool.
The other two loops
A while loop earns its place when you repeat work as long as a condition holds and there is no counter to advance. You state the condition, and the loop runs until it turns false.
A do-while loop tests its condition after the body. That means the body always runs once before the first test.
// Rows.java
public class Rows {
public static void main(String[] args) {
int[] empty = {};
int i = 0;
while (i < empty.length) {
System.out.println("while row " + i);
i++;
}
int j = 0;
do {
System.out.println("do-while row " + j);
j++;
} while (j < empty.length);
}
}
$ javac Rows.java && java Rows
do-while row 0
The while loop never runs on the empty array, because its condition is false before the body starts, and the do-while loop runs once. Inside any of these loops, break leaves the loop early and continue jumps to the next pass. As a rule of thumb, pick the loop whose stopping condition you can state without arithmetic.
Before the next article
Remove one price from the array, so it holds three values such as {12000, 15000, 10500}. Run the fixed program. It prints items: 3, total: 37500 and average: 12500, and all three lines are correct.
Now run the unchanged checker. It reports 0 of 3 checks passed, even though the code is now correct, because it expects four items. The checker's expectations are fixed at four items, so it tests the input as much as it tests the code.
Which line of your own last loop would you have to change if the array grew by one tomorrow?