4.9.1 Visiting Every Element by Index
A music app keeps a playlist in an ArrayList<String>. To show the playlist on screen, the app has to look at every song, one after another. In Topic 4.8 you could reach one element at a time with get, but writing songs.get(0), songs.get(1), songs.get(2), … by hand only works if you know exactly how many songs there are. A playlist can have 4 songs today and 40 tomorrow.
You solved the same problem for arrays in Topic 4.4: let a loop variable count through the indices, and use it to reach each element in turn. The same plan works for an ArrayList. Only two details change:
- An array tells you its length with
.length(no parentheses). AnArrayListtells you how many elements it holds with the method callsize(). - An array element is
scores[i]. AnArrayListelement issongs.get(i).
Using a loop to visit all the elements of a list, or an ordered run of them (for example, every other one, or the last three), is called traversing the list. One such pass is a traversal.
Key idea
To traverse an ArrayList by index, count i from 0 while i < list.size(), and reach each element with
list.get(i).
Worked example: Printing a playlist with its positions
Look at the loop header on line 13. It starts i at the first index and stops before songs.size().
import java.util.ArrayList;
public class PlaylistIndexed
{
public static void main(String[] args)
{
ArrayList<String> songs = new ArrayList<String>();
songs.add("Sunrise");
songs.add("Rain Song");
songs.add("Night Drive");
songs.add("Echoes");
for (int i = 0; i < songs.size(); i++)
{
System.out.println(i + ": " + songs.get(i));
}
}
}The list holds 4 songs, so songs.size() returns 4 and the valid indices are 0, 1, 2, and 3.
i |
i < songs.size() |
songs.get(i) |
Printed |
|---|---|---|---|
| 0 | 0 < 4 is true |
"Sunrise" |
0: Sunrise |
| 1 | 1 < 4 is true |
"Rain Song" |
1: Rain Song |
| 2 | 2 < 4 is true |
"Night Drive" |
2: Night Drive |
| 3 | 3 < 4 is true |
"Echoes" |
3: Echoes |
| 4 | 4 < 4 is false |
loop ends |
0: Sunrise
1: Rain Song
2: Night Drive
3: EchoesTwo things make this loop safe for a playlist of any length. First, the test calls songs.size() each time it runs, so it always uses the current number of songs. Second, < stops the loop at the last valid index, size() - 1, because the indices start at 0.
A traversal does not have to start at the front or visit every element. Because the loop header is an ordinary for loop from Topic 2.8, you can change the start, the test, and the update to walk through the list in a different order.
Worked example: Backward, and every other song
To play the list in reverse, start at the last index, songs.size() - 1, and count down while i >= 0:
import java.util.ArrayList;
public class PlaylistBackward
{
public static void main(String[] args)
{
ArrayList<String> songs = new ArrayList<String>();
songs.add("Sunrise");
songs.add("Rain Song");
songs.add("Night Drive");
songs.add("Echoes");
for (int i = songs.size() - 1; i >= 0; i--)
{
System.out.println(songs.get(i));
}
}
}With 4 songs, i takes the values 3, 2, 1, 0, so this prints:
Echoes
Night Drive
Rain Song
SunriseThe test is i >= 0, not i > 0, because index 0 holds a real song, "Sunrise", which must not be left out.
A while loop can traverse a list too, as it could an array in Topic 4.4. This one has five songs and adds 2 to i each pass, so it visits only the songs at even indices:
import java.util.ArrayList;
public class PlaylistWhile
{
public static void main(String[] args)
{
ArrayList<String> songs = new ArrayList<String>();
songs.add("Sunrise");
songs.add("Rain Song");
songs.add("Night Drive");
songs.add("Echoes");
songs.add("Last Call");
int i = 0;
while (i < songs.size())
{
System.out.println(songs.get(i));
i += 2;
}
}
}i |
i < songs.size() |
Printed |
|---|---|---|
| 0 | 0 < 5 is true |
Sunrise |
| 2 | 2 < 5 is true |
Night Drive |
| 4 | 4 < 5 is true |
Last Call |
| 6 | 6 < 5 is false |
loop ends |
The pieces of the for loop are all still there, just spread out: int i = 0; before the loop, the test as the while condition, and i += 2; as the last line of the body.
Watch out
Writing array syntax on an ArrayList. Arrays and ArrayLists look alike, so students often reach for the array forms by habit:
import java.util.ArrayList;
public class ListLength
{
public static void main(String[] args)
{
ArrayList<String> songs = new ArrayList<String>();
songs.add("Sunrise");
songs.add("Echoes");
for (int i = 0; i < songs.length; i++)
{
System.out.println(songs[i]);
}
}
}This does not compile. Java reports two errors:
ListLength.java:11: error: cannot find symbol
symbol: variable length
ListLength.java:13: error: array required, but ArrayList<String> found(Exact wording can vary between Java versions.) An ArrayList is an object, not an array. It has no length variable, and square brackets only work on arrays. The fix is songs.size() on line 11 and songs.get(i) on line 13. A memory aid: arrays use .length and [ ]; ArrayLists use method calls with parentheses, size() and get(i).
Pause and try
Question 1 · Multiple choice
names is an ArrayList<String> holding at least two names. Which loop header makes names.get(i) visit every name exactly once, from first to last?
for (int i = 0; i < names.size(); i++)for (int i = 0; i < names.length; i++)for (int i = 0; i < names.size() - 1; i++)for (int i = 1; i < names.size(); i++)
Check your reasoning
A. The indices of an ArrayList run from 0 to size() - 1. Starting at 0 and stopping when i < names.size() becomes false visits each of those indices once.
- B is wrong: an
ArrayListhas nolengthvariable, so this does not compile (cannot find symbol). That is array syntax. - C is wrong: the last value of
iissize() - 2, so the last name is never visited. This is an off by one error. - D is wrong: starting at
1skips the first name, which is at index0.
Reread 4.9.1 Visiting Every Element by Index if you chose differently.
Question 2 · Tracing
What does this program print? Show the value of i for each pass.
import java.util.ArrayList;
public class PrizeTrace
{
public static void main(String[] args)
{
ArrayList<String> prizes = new ArrayList<String>();
prizes.add("pen");
prizes.add("mug");
prizes.add("hat");
prizes.add("bag");
prizes.add("cap");
for (int i = 1; i < prizes.size(); i += 2)
{
System.out.println(i + " " + prizes.get(i));
}
}
}Check your reasoning
1 mug
3 bagThe list is pen (0), mug (1), hat (2), bag (3), cap (4), so prizes.size() is 5.
i |
i < 5 |
Printed |
|---|---|---|
| 1 | true |
1 mug |
| 3 | true |
3 bag |
| 5 | false |
The loop starts at index 1 and steps by 2, so it visits only the odd indices. If you printed pen, you started at 0; if you printed cap, you stepped by 1 at some point.
Reread 4.9.1 Visiting Every Element by Index if your answer differed.
4.9.2 Staying Inside the Index Range
The loop in 4.9.1 stopped at exactly the right moment. What happens if a loop goes one step too far? With a String in Topic 1.15, asking for a character past the end crashed the program. With arrays in Topic 4.3, asking for a missing index crashed it too. An ArrayList follows the same rule, for the same reason: there is no element there to return, and Java refuses to make one up.
When a program calls get, set, or remove with an index less than 0 or greater than size() - 1, Java stops the program with an IndexOutOfBoundsException, a runtime error meaning “that index is outside the list.” The program compiles, because the compiler cannot know how big the list will be when the line runs. The problem only appears when the line actually runs with a bad index.
Key idea
The valid indices of an ArrayList are 0 through size() - 1. Any other index makes get, set, or remove throw an
IndexOutOfBoundsException.
Worked example: One pass too many
This loop is meant to print three scores. Compare its test on line 12 with the one in 4.9.1.
import java.util.ArrayList;
public class ScoresTooFar
{
public static void main(String[] args)
{
ArrayList<Integer> scores = new ArrayList<Integer>();
scores.add(88);
scores.add(73);
scores.add(95);
for (int i = 0; i <= scores.size(); i++)
{
System.out.println(scores.get(i));
}
System.out.println("Done");
}
}i |
i <= scores.size() |
What happens |
|---|---|---|
| 0 | 0 <= 3 is true |
prints 88 |
| 1 | 1 <= 3 is true |
prints 73 |
| 2 | 2 <= 3 is true |
prints 95 |
| 3 | 3 <= 3 is true |
scores.get(3): there is no index 3. Crash |
The output is the three scores, then an error message:
88
73
95
Exception in thread "main" java.lang.IndexOutOfBoundsException: Index 3 out of bounds for length 3(The exact message can vary between Java versions, and several more lines follow it that name the line where the crash happened.) Notice that Done never prints. When an exception stops a program, nothing after the crash runs.
The cause is <=. It lets i reach size(), which is one past the last index. The fix is the test from 4.9.1, i < scores.size().
The same mistake can happen at the other end of a backward traversal. Change one thing from the backward loop in 4.9.1: start at size() instead of size() - 1.
Worked example: Starting a backward loop one too high
import java.util.ArrayList;
public class BackwardFromSize
{
public static void main(String[] args)
{
ArrayList<Integer> scores = new ArrayList<Integer>();
scores.add(88);
scores.add(73);
scores.add(95);
for (int i = scores.size(); i >= 0; i--)
{
System.out.println(scores.get(i));
}
}
}The list holds 3 scores, so the first value of i is 3. The very first call, scores.get(3), asks for an index that does not exist. Nothing prints at all before the program stops with java.lang.IndexOutOfBoundsException: Index 3 out of bounds for length 3. The fix is to start at the last real index: int i = scores.size() - 1.
Watch out
Treating the size as an index. size() counts elements; it is not a position. A list of 3 elements has positions 0, 1, and 2, so size() is always one more than the last index. Students who write <= list.size(), or start a backward loop at list.size(), are using the count where they need a position. Two quick checks before you run a traversal: the first index used should be 0 (or size() - 1 going backward), and the last one should be size() - 1 (or 0 going backward).
Pause and try
Question 3 · Multiple choice
What happens when this program runs?
import java.util.ArrayList;
public class ShiftedStart
{
public static void main(String[] args)
{
ArrayList<String> colors = new ArrayList<String>();
colors.add("red");
colors.add("blue");
colors.add("green");
colors.add("gold");
for (int i = 1; i <= colors.size(); i++)
{
System.out.println(colors.get(i));
}
}
}- It prints
red,blue,green, andgold. - It prints
blue,green, andgold, and then stops with an IndexOutOfBoundsException. - It prints
blue,green, andgold, and then ends normally. - It does not compile, because
colors.size()cannot be used in a loop test.
Check your reasoning
B. The loop starts at index 1, so it skips red. It prints colors.get(1), colors.get(2), and colors.get(3), which are blue, green, and gold. Then i becomes 4, and 4 <= 4 is still true, so the program calls colors.get(4). The valid indices are 0 to 3, so Java throws an IndexOutOfBoundsException.
- A is wrong: the loop starts at 1, so index 0 (
red) is never visited. Indices start at 0, not 1. - C is wrong:
<=letsireachsize(), which is one past the last index. - D is wrong: a method call can appear in a loop test; Java calls it each time the test runs. The bad index is a runtime problem, not a compile error.
Reread 4.9.2 Staying Inside the Index Range if you chose differently.
Question 4 · Short answer
A student says, “list.get(list.size()) gets the last element, because size() is how far the list goes.” Explain what that call really does, and what the student should write instead.
Check your reasoning
Model answer: The call throws an IndexOutOfBoundsException, for any list, even an empty one. Indices start at 0, so a list with n elements has indices 0 through n - 1, and size() returns n. That is one past the last index, where no element exists. The last element is list.get(list.size() - 1).
A complete answer must:
- say that the call throws an IndexOutOfBoundsException,
- explain that indices start at 0, so the last index is
size() - 1, and - give the correct call,
list.get(list.size() - 1).
Reread 4.9.2 Staying Inside the Index Range if your answer differed.
4.9.3 Traversing with an Enhanced for Loop
Many traversals never use the index at all. Printing each song only needs the song, not its position. In Topic 4.4 you met a shorter loop for exactly this job on arrays, the enhanced for loop. It works on an ArrayList in the same way, and it removes the chance of an index mistake, because there is no index to get wrong.
Here is the form, read aloud as “for each song in songs”:
for (String song : songs)
Stringis the type of each element. It must match the list’s element type, theEinArrayList<E>.songis the enhanced for loop variable. On each pass, Java assigns it the next element of the list, starting with the element at index 0.- The colon separates the variable from the list being traversed.
Worked example: The playlist again, with no index
import java.util.ArrayList;
public class PlaylistEnhanced
{
public static void main(String[] args)
{
ArrayList<String> songs = new ArrayList<String>();
songs.add("Sunrise");
songs.add("Rain Song");
songs.add("Night Drive");
songs.add("Echoes");
for (String song : songs)
{
System.out.println("Now playing: " + song);
}
}
}| Pass | song holds |
Printed |
|---|---|---|
| 1 | "Sunrise" |
Now playing: Sunrise |
| 2 | "Rain Song" |
Now playing: Rain Song |
| 3 | "Night Drive" |
Now playing: Night Drive |
| 4 | "Echoes" |
Now playing: Echoes |
The loop stops by itself after the last element. There is no size(), no get, and no i. The trade-off is that the loop always goes front to back, one element at a time; for backward or every-other traversals, use an indexed loop.
For a list of Integer, the loop variable can be an int. Each Integer element is unboxed into an int as it is assigned, which is the unboxing from Topic 4.7.
Worked example: Unboxing in the loop header
import java.util.ArrayList;
public class TempsEnhanced
{
public static void main(String[] args)
{
ArrayList<Integer> temps = new ArrayList<Integer>();
temps.add(64);
temps.add(71);
temps.add(58);
for (int temp : temps)
{
int fahrenheitAboveFreezing = temp - 32;
System.out.println(temp + " is " + fahrenheitAboveFreezing + " above freezing");
}
}
}On each pass, temp gets a plain int copy of the next element, so line 14 can do arithmetic with it directly:
64 is 32 above freezing
71 is 39 above freezing
58 is 26 above freezingWriting for (Integer temp : temps) would also work; the arithmetic would unbox temp instead.
What the loop variable can and cannot change
The loop variable is a separate variable that is handed a copy of each element. That one fact explains two behaviors that look contradictory until you see it.
Assigning to the loop variable changes only the variable. In the next program, the goal is to make every word uppercase. (Printing an ArrayList with println shows its elements in square brackets, separated by commas.)
import java.util.ArrayList;
public class ShoutWrong
{
public static void main(String[] args)
{
ArrayList<String> words = new ArrayList<String>();
words.add("go");
words.add("team");
words.add("win");
for (String word : words)
{
word = word.toUpperCase();
}
System.out.println(words);
}
}[go, team, win]
Nothing changed. On each pass, line 14 builds a new uppercase String (Strings are immutable, Topic 1.15) and stores its reference in word. The list still refers to the original lowercase strings; word was only a copy of the reference, so pointing it at a new string leaves the list’s slot alone. This matches the array rule from Topic 4.4.
To replace what is stored in the list, you need the slot’s index so you can call set. That calls for an indexed loop:
import java.util.ArrayList;
public class ShoutFixed
{
public static void main(String[] args)
{
ArrayList<String> words = new ArrayList<String>();
words.add("go");
words.add("team");
words.add("win");
for (int i = 0; i < words.size(); i++)
{
words.set(i, words.get(i).toUpperCase());
}
System.out.println(words);
}
}[GO, TEAM, WIN]
Calling a method on the loop variable can change the object. When the list holds references to mutable objects, the loop variable refers to the very same object as the list does. They are aliases, as when a reference is passed to a method in Topic 3.6. Calling a mutator through the loop variable changes that shared object.
Java allows only one public class per file, so the helper class here leaves off public; in a real project each class gets its own file.
Worked example: Giving every player a bonus
Player has a private point total and a mutator, addPoints. The first loop in main calls the mutator on each player.
import java.util.ArrayList;
class Player
{
private String name;
private int points;
public Player(String playerName, int startPoints)
{
name = playerName;
points = startPoints;
}
public String getName()
{
return name;
}
public int getPoints()
{
return points;
}
public void addPoints(int amount)
{
points = points + amount;
}
}
public class TeamBonus
{
public static void main(String[] args)
{
ArrayList<Player> team = new ArrayList<Player>();
team.add(new Player("Ada", 10));
team.add(new Player("Ben", 7));
for (Player p : team)
{
p.addPoints(5);
}
for (Player p : team)
{
System.out.println(p.getName() + " " + p.getPoints());
}
}
}On the first pass of the loop on line 38, p refers to the same object as the list’s element 0, Ada’s Player. Line 40 adds 5 to that object’s points. On the second pass, p refers to Ben’s object. The list’s references never change, but the objects they lead to do:
Ada 15
Ben 12Inside for (Player p : team) |
Changes the list? | Why |
|---|---|---|
p = new Player("Zed", 0); |
No | Only the copy in p points somewhere new |
p.addPoints(5); |
The object, yes | p and the list refer to the same object |
team.set(...) to replace an element |
Needs an index | Use an indexed loop; the enhanced loop has no i |
Key idea
An enhanced for loop gives its variable a copy of each element. Assigning to that variable never changes the list, but calling a mutator through it changes the object the list refers to.
Watch out
Expecting the loop variable to write back. Students write word = word.toUpperCase(); or n = n * 2; inside an enhanced for loop and expect the list to change, because the variable “is” the element. It is only a copy. The code compiles and runs, but the list is unchanged afterward. When you need to replace elements, use an indexed loop with set(i, …).
Pause and try
Question 5 · Tracing
What does this program print?
import java.util.ArrayList;
public class DoubleLoopVar
{
public static void main(String[] args)
{
ArrayList<Integer> nums = new ArrayList<Integer>();
nums.add(4);
nums.add(3);
nums.add(5);
for (int n : nums)
{
n = n * 2;
System.out.println(n);
}
System.out.println(nums);
}
}Check your reasoning
8
6
10
[4, 3, 5]| Pass | n after unboxing |
n after n = n * 2 |
Printed |
|---|---|---|---|
| 1 | 4 | 8 | 8 |
| 2 | 3 | 6 | 6 |
| 3 | 5 | 10 | 10 |
Inside the loop, n really is doubled, so the doubled values print. But n is a copy of each element, so the list still holds 4, 3, and 5 when line 17 prints it. If your last line was [8, 6, 10], you treated the loop variable as if it were the list’s slot.
Reread 4.9.3 Traversing with an Enhanced for Loop if your answer differed.
Question 6 · Multiple choice
team is an ArrayList<Player>, using the Player class from 4.9.3. Which loop body, placed inside for (Player p : team), changes what a later traversal of team will see?
p = new Player(p.getName(), 100);p.addPoints(100);int total = p.getPoints() + 100;- None of them; an enhanced for loop can never affect the list.
Check your reasoning
B. p refers to the same Player object as the list does, so calling the mutator addPoints changes that object. A later loop over team reaches the same objects and sees the new totals.
- A is wrong: this makes
prefer to a brand-new object. The list still refers to the original player, which is unchanged. - C is wrong: it computes a value in a local variable and never changes any object.
- D is wrong: the loop cannot change which references the list holds, but it can change the objects those references lead to.
Reread 4.9.3 Traversing with an Enhanced for Loop if you chose differently.
4.9.4 Removing Elements Without Skipping
A teacher keeps quiz scores in an ArrayList<Integer> and wants to drop every score below 70. In Topic 4.8 you removed one element at a known index with remove(i). Here the program has to look at each score and decide, so remove goes inside a traversal. The obvious loop looks right, compiles, and runs without crashing, yet it sometimes gives the wrong answer.
The problem comes from what remove does to the list, which you saw in Topic 4.8: every element after the removed one slides one place to the left, and size() drops by 1. So the element that was at index i + 1 is now at index i. If the loop then does i++, it moves on to index i + 1, and the element that just slid into index i is never checked. It has been skipped.
Picture a line of people being checked by a guard who steps forward one position after each check. If the person in front of the guard leaves, everyone behind shuffles forward. The guard still steps forward one place, so the person who shuffled into the spot in front of the guard is never checked.
Worked example: A score that slips past
Two low scores, 62 and 58, sit next to each other. Watch what happens to 58.
import java.util.ArrayList;
public class RemoveSkip
{
public static void main(String[] args)
{
ArrayList<Integer> scores = new ArrayList<Integer>();
scores.add(85);
scores.add(62);
scores.add(58);
scores.add(90);
for (int i = 0; i < scores.size(); i++)
{
if (scores.get(i) < 70)
{
scores.remove(i);
}
}
System.out.println(scores);
}
}i |
List before this pass | scores.get(i) |
Action |
|---|---|---|---|
| 0 | [85, 62, 58, 90] |
85 | keep |
| 1 | [85, 62, 58, 90] |
62 | remove index 1; 58 slides to index 1 |
| 2 | [85, 58, 90] |
90 | keep (58 at index 1 was never checked) |
| 3 | [85, 58, 90] |
3 < 3 is false; loop ends |
[85, 58, 90]58 is below 70 but survived. After the removal on line 17, the loop’s i++ jumped straight from index 1 to index 2, past the score that had just moved into index 1.
There are two common ways to remove without skipping. Both make sure that every element is checked exactly once, even after a shift.
Worked example: Fix 1: traverse backward
import java.util.ArrayList;
public class RemoveBackward
{
public static void main(String[] args)
{
ArrayList<Integer> scores = new ArrayList<Integer>();
scores.add(85);
scores.add(62);
scores.add(58);
scores.add(90);
for (int i = scores.size() - 1; i >= 0; i--)
{
if (scores.get(i) < 70)
{
scores.remove(i);
}
}
System.out.println(scores);
}
}i |
List before this pass | scores.get(i) |
Action |
|---|---|---|---|
| 3 | [85, 62, 58, 90] |
90 | keep |
| 2 | [85, 62, 58, 90] |
58 | remove index 2 |
| 1 | [85, 62, 90] |
62 | remove index 1 |
| 0 | [85, 90] |
85 | keep |
[85, 90]When remove(i) shifts elements, only the elements after index i move, and going backward the loop has already checked all of them. The elements it has not checked yet, at smaller indices, stay exactly where they were. So nothing is skipped.
Worked example: Fix 2: move forward only when nothing was removed
import java.util.ArrayList;
public class RemoveStayPut
{
public static void main(String[] args)
{
ArrayList<Integer> scores = new ArrayList<Integer>();
scores.add(85);
scores.add(62);
scores.add(58);
scores.add(90);
int i = 0;
while (i < scores.size())
{
if (scores.get(i) < 70)
{
scores.remove(i);
}
else
{
i++;
}
}
System.out.println(scores);
}
}This while loop adds 1 to i only in the else branch, when the score was kept. When a score is removed, i stays put, so the next pass checks the element that just slid into index i.
i |
List before this pass | scores.get(i) |
Action |
|---|---|---|---|
| 0 | [85, 62, 58, 90] |
85 | keep; i becomes 1 |
| 1 | [85, 62, 58, 90] |
62 | remove; i stays 1 |
| 1 | [85, 58, 90] |
58 | remove; i stays 1 |
| 1 | [85, 90] |
90 | keep; i becomes 2 |
| 2 | [85, 90] |
2 < 2 is false; loop ends |
[85, 90]The test i < scores.size() calls size() every time, so it always uses the list’s current, shrinking length.
Key idea
Removing at index i slides the later elements left. A forward loop that still does i++ after a removal skips the
element that moved into index i. Traverse backward, or advance i only when nothing was removed.
Watch out
Trusting one test run. The skipping bug only shows up when two elements to be removed sit next to each other. With the scores [62, 85, 58, 90], the forward loop from the first example happens to produce the right answer, [85, 90], because the element that slides into place after each removal is a score that should stay. A student who tests only that list will think the loop works. Always ask: “What if two elements in a row need to be removed?”
Pause and try
Question 7 · Tracing
This loop is supposed to remove every word shorter than 4 letters. What does it print? Show the list before each pass.
import java.util.ArrayList;
public class WordsSkip
{
public static void main(String[] args)
{
ArrayList<String> words = new ArrayList<String>();
words.add("the");
words.add("an");
words.add("apple");
words.add("of");
words.add("pie");
for (int i = 0; i < words.size(); i++)
{
if (words.get(i).length() < 4)
{
words.remove(i);
}
}
System.out.println(words);
}
}Check your reasoning
[an, apple, pie]i |
List before this pass | words.get(i) |
Action |
|---|---|---|---|
| 0 | [the, an, apple, of, pie] |
the (3) |
remove; an slides to index 0 |
| 1 | [an, apple, of, pie] |
apple (5) |
keep |
| 2 | [an, apple, of, pie] |
of (2) |
remove; pie slides to index 2 |
| 3 | [an, apple, pie] |
3 < 3 is false; loop ends |
an and pie are both shorter than 4 letters, but each slid into the index the loop had just finished with, and i++ moved past it. If you printed [apple], you described what the loop was meant to do, not what it does.
Reread 4.9.4 Removing Elements Without Skipping if your answer differed.
Question 8 · Debugging
Correct the loop in Question 7 so that it removes every word shorter than 4 letters. Change only the loop header.
Check your reasoning
The forward loop skips the element that slides left after each removal. Traverse backward instead, so that every shift only moves elements the loop has already checked:
import java.util.ArrayList;
public class WordsFixed
{
public static void main(String[] args)
{
ArrayList<String> words = new ArrayList<String>();
words.add("the");
words.add("an");
words.add("apple");
words.add("of");
words.add("pie");
for (int i = words.size() - 1; i >= 0; i--)
{
if (words.get(i).length() < 4)
{
words.remove(i);
}
}
System.out.println(words);
}
}This prints [apple]. Going backward, the loop checks pie, of, apple, an, the in that order, and every removal only shifts words at higher indices, which were already checked. The original compiled and ran without an error; it just quietly left an and pie in the list.
(Turning it into a while loop that advances only when a word is kept also works, but that changes more than the header.)
Reread 4.9.4 Removing Elements Without Skipping if you missed it.
4.9.5 Changing the Size in an Enhanced for Loop
An enhanced for loop would seem like a tidy way to remove items: “for each task, do it, then take it off the list.” But the enhanced for loop keeps track of where it is in the list behind the scenes, and it assumes the list keeps the same length for the whole traversal. If the loop body adds or removes an element, the loop’s idea of “the next element” no longer matches the real list.
Rather than quietly skipping or repeating elements, Java checks for this. When the enhanced for loop goes to fetch the next element and finds that the list’s size was changed during the traversal, it stops the program with a ConcurrentModificationException. “Concurrent modification” means the list was modified while it was being traversed.
Worked example: Removing during an enhanced for loop
The plan is to print each task and then remove it from the front of the list.
import java.util.ArrayList;
public class TaskListCrash
{
public static void main(String[] args)
{
ArrayList<String> tasks = new ArrayList<String>();
tasks.add("email");
tasks.add("laundry");
tasks.add("homework");
for (String task : tasks)
{
System.out.println("Done: " + task);
tasks.remove(0);
}
System.out.println("All tasks finished");
}
}On the first pass, task is "email", so line 14 prints Done: email. Line 15 removes element 0, so the list is now [laundry, homework]. The body ends, and the loop goes back to line 12 to fetch the next element. It sees that the list changed size and throws:
Done: email
Exception in thread "main" java.util.ConcurrentModificationException(More lines follow, ending with one that names line 12, the loop header, since that is where the next element was being fetched. Wording can vary.) All tasks finished never prints.
Since the job is “keep handling the first task until none are left,” a while loop that checks the size and uses indices does it correctly:
import java.util.ArrayList;
public class TaskListFixed
{
public static void main(String[] args)
{
ArrayList<String> tasks = new ArrayList<String>();
tasks.add("email");
tasks.add("laundry");
tasks.add("homework");
while (tasks.size() > 0)
{
System.out.println("Done: " + tasks.get(0));
tasks.remove(0);
}
System.out.println("All tasks finished");
}
}Done: email
Done: laundry
Done: homework
All tasks finishedAdding is just as much of a problem as removing. Change one thing: the loop body adds an element instead of removing one.
Worked example: Adding during an enhanced for loop
import java.util.ArrayList;
public class CartAddCrash
{
public static void main(String[] args)
{
ArrayList<String> cart = new ArrayList<String>();
cart.add("milk");
cart.add("eggs");
cart.add("rice");
for (String item : cart)
{
System.out.println(item);
if (item.equals("milk"))
{
cart.add("cereal");
}
}
}
}The first pass prints milk, and because the item equals "milk", line 17 appends "cereal". When the loop fetches the next element, the list has grown, so Java throws a ConcurrentModificationException after printing only milk.
An indexed loop has no hidden position to fall out of step. Its test calls cart.size() on every pass, so it simply sees the longer list:
import java.util.ArrayList;
public class CartAddFixed
{
public static void main(String[] args)
{
ArrayList<String> cart = new ArrayList<String>();
cart.add("milk");
cart.add("eggs");
cart.add("rice");
for (int i = 0; i < cart.size(); i++)
{
System.out.println(cart.get(i));
if (cart.get(i).equals("milk"))
{
cart.add("cereal");
}
}
}
}milk
eggs
rice
cerealThe new item is added at the end, after the current index, so the loop reaches it on its last pass.
Java does not promise to catch every size change in an enhanced for loop; occasionally such a loop just ends early with no exception. You cannot count on either outcome, so the rule is simple.
Key idea
Never add or remove elements while traversing an ArrayList with an enhanced for loop. Use an indexed for loop or a while loop, where you control the index, whenever the size will change.
| Traversal | Can it change the size safely? | What to watch for |
|---|---|---|
Indexed for, forward |
Yes, with care | Removing skips the next element |
Indexed for, backward |
Yes | Start at size() - 1 |
while, advance on keep |
Yes | Do not i++ after a removal |
Enhanced for |
No | ConcurrentModificationException |
Watch out
Keeping your own counter in an enhanced for loop. Since the enhanced loop has no index, some students add one by hand and call remove(index). That still changes the size inside an enhanced for loop, so it still causes the exception. (Question 14 shows this.) If you find yourself needing an index, that is the sign to switch to an indexed loop.
Pause and try
Question 9 · Multiple choice
What happens when this program runs?
import java.util.ArrayList;
public class GuestAdd
{
public static void main(String[] args)
{
ArrayList<String> guests = new ArrayList<String>();
guests.add("Ana");
guests.add("Kim");
guests.add("Lu");
for (String guest : guests)
{
System.out.println(guest);
if (guest.equals("Kim"))
{
guests.add("Kim's friend");
}
}
}
}- It prints
Ana,Kim,Lu, andKim's friend. - It prints
Ana,Kim, andLu, and ends normally. - It prints
AnaandKim, and then stops with a ConcurrentModificationException. - It does not compile, because
addcannot be called inside an enhanced for loop.
Check your reasoning
C. The first pass prints Ana. The second pass prints Kim and, because the guest is "Kim", adds a new element. When the enhanced for loop goes to fetch the third element, the list has changed size, so Java throws a ConcurrentModificationException.
- A is wrong: that is what an indexed loop with
i < guests.size()would print, since its test sees the new size. An enhanced for loop does not adjust to the change. - B is wrong: the loop does not ignore the change and carry on; Java detects it when fetching the next element.
- D is wrong: the code compiles. The compiler does not check what the loop body does to the list; the problem appears only at run time.
Reread 4.9.5 Changing the Size in an Enhanced for Loop if you chose differently.
Question 10 · Short answer
In 4.9.4, the forward indexed loop could be fixed by not moving i forward after a removal. Explain why that kind of fix is impossible in an enhanced for loop.
Check your reasoning
Model answer: The fix works by controlling the index: after a removal, the loop keeps i where it is so the element that slid into that index gets checked. An enhanced for loop has no index variable you can see or change; Java moves to the next element by itself. So there is nothing to hold back. On top of that, changing the size inside an enhanced for loop can throw a ConcurrentModificationException, so the loop must not remove elements at all.
A complete answer must:
- say that the 4.9.4 fix depends on controlling the index,
- say that an enhanced for loop has no index the programmer can adjust, and
- mention that removing inside an enhanced for loop can cause a ConcurrentModificationException.
Reread 4.9.4 Removing Elements Without Skipping and 4.9.5 Changing the Size in an Enhanced for Loop if your answer differed.
Putting it together
A weather station records one temperature reading per hour in an ArrayList<Integer>. When the sensor fails, it records -999 instead of a real reading. This program prints the raw readings with their positions, removes every failed reading, and prints what is left. It uses three different traversals, each chosen for its job. Predict the output before reading the trace.
Worked example: Cleaning up sensor readings
import java.util.ArrayList;
public class SensorCleanup
{
public static void main(String[] args)
{
ArrayList<Integer> readings = new ArrayList<Integer>();
readings.add(72);
readings.add(-999);
readings.add(-999);
readings.add(68);
readings.add(75);
readings.add(-999);
System.out.println("Raw readings:");
for (int i = 0; i < readings.size(); i++)
{
System.out.println(i + ": " + readings.get(i));
}
for (int i = readings.size() - 1; i >= 0; i--)
{
if (readings.get(i) == -999)
{
readings.remove(i);
}
}
System.out.println("Clean readings:");
for (int reading : readings)
{
System.out.println(reading);
}
System.out.println("Readings kept: " + readings.size());
}
}Lines 16–19: an indexed for loop, because the output needs each position as well as the value. It runs i from 0 to 5 and prints 0: 72 through 5: -999.
Lines 21–27: a backward indexed loop, because it removes elements and must not skip any. Two failed readings sit side by side at indices 1 and 2.
i |
List before this pass | readings.get(i) |
Action |
|---|---|---|---|
| 5 | [72, -999, -999, 68, 75, -999] |
-999 | remove index 5 |
| 4 | [72, -999, -999, 68, 75] |
75 | keep |
| 3 | [72, -999, -999, 68, 75] |
68 | keep |
| 2 | [72, -999, -999, 68, 75] |
-999 | remove index 2 |
| 1 | [72, -999, 68, 75] |
-999 | remove index 1 |
| 0 | [72, 68, 75] |
72 | keep |
Lines 30–33: an enhanced for loop, because it only reads each value, in order, and does not change the size. Each Integer is unboxed into the int variable reading.
Raw readings:
0: 72
1: -999
2: -999
3: 68
4: 75
5: -999
Clean readings:
72
68
75
Readings kept: 3If lines 21–27 had used a forward loop with i++, the second -999 would have slid into index 1 right after the first was removed and been skipped, leaving [72, -999, 68, 75]. And if the removal had been done in an enhanced for loop, the program would have stopped with a ConcurrentModificationException on the first removal.
Vocabulary
| Term | Definition | Example |
|---|---|---|
| traversing (a traversal) | Using a loop to visit all the elements of a list, or an ordered run of them | for (int i = 0; i < list.size(); i++) |
| IndexOutOfBoundsException | The runtime error thrown when get, set, or remove gets an index below 0 or above size() - 1 |
list.get(list.size()) |
| enhanced for loop | A loop that hands its variable each element in order, without using indices | for (String s : names) |
| enhanced for loop variable | The variable in an enhanced for loop header; it holds a copy of the current element | s in for (String s : names) |
| skipped (element) | An element a forward loop never checks because a removal slid it into an index already passed | 58 in [85, 62, 58, 90] |
| ConcurrentModificationException | The runtime error an enhanced for loop can throw when the list’s size changes during the traversal | names.add("x") inside for (String s : names) |
Chapter summary
- Traversing an ArrayList by index works like traversing an array, with
size()in place of.lengthandget(i)in place of[i]. Changing the start, test, and update lets a for or while loop go backward or visit only some elements. - Valid indices run from 0 to
size() - 1. Usingsize()itself, or any other index outside that range, throws an IndexOutOfBoundsException at run time. - An enhanced for loop visits every element front to back with no index. Its variable holds a copy of each element: assigning to it never changes the list, but calling a mutator through it changes the shared object.
- Removing at index
islides later elements left, so a forward loop that always doesi++skips elements. Traverse backward, or advance only when nothing was removed. - Adding or removing elements inside an enhanced for loop can throw a ConcurrentModificationException. When the size will change, use an indexed for loop or a while loop.
Review questions
Question 11 · Multiple choice
A bus app stores its stops in order in stops, an ArrayList<String> holding Main, Oak, and Pine. Which loop prints all three stops in reverse order, Pine, Oak, Main, and then ends normally? Each loop has the body System.out.println(stops.get(i));.
for (int i = stops.size() - 1; i >= 0; i--)for (int i = stops.size(); i >= 0; i--)for (int i = stops.size() - 1; i > 0; i--)for (int i = stops.size() - 1; i >= 0; i++)
Check your reasoning
A. It starts at the last index, 2, and counts down through 1 and 0, printing Pine, Oak, Main. Then i becomes -1 and -1 >= 0 is false:
import java.util.ArrayList;
public class BackStopsA
{
public static void main(String[] args)
{
ArrayList<String> stops = new ArrayList<String>();
stops.add("Main");
stops.add("Oak");
stops.add("Pine");
for (int i = stops.size() - 1; i >= 0; i--)
{
System.out.println(stops.get(i));
}
}
}- B is wrong: it starts at
stops.size(), which is 3. The very first call,stops.get(3), throws an IndexOutOfBoundsException before anything prints. - C is wrong: the test
i > 0isfalsewhenireaches 0, so it prints onlyPineandOak.Main, at index 0, is left out. - D is wrong: the update counts up instead of down. It prints
Pine, thenibecomes 3,3 >= 0istrue, andstops.get(3)throws an IndexOutOfBoundsException.
Reread 4.9.1 Visiting Every Element by Index and 4.9.2 Staying Inside the Index Range if you chose differently.
Question 12 · Tracing
A seating chart marks empty seats with "x". This loop is meant to remove every "x". What does it print, and why is one "x" left?
import java.util.ArrayList;
public class SeatsSkip
{
public static void main(String[] args)
{
ArrayList<String> seats = new ArrayList<String>();
seats.add("x");
seats.add("x");
seats.add("Ana");
seats.add("x");
seats.add("Bo");
for (int i = 0; i < seats.size(); i++)
{
if (seats.get(i).equals("x"))
{
seats.remove(i);
}
}
System.out.println(seats);
}
}Check your reasoning
[x, Ana, Bo]i |
List before this pass | seats.get(i) |
Action |
|---|---|---|---|
| 0 | [x, x, Ana, x, Bo] |
x |
remove; second x slides to 0 |
| 1 | [x, Ana, x, Bo] |
Ana |
keep |
| 2 | [x, Ana, x, Bo] |
x |
remove; Bo slides to 2 |
| 3 | [x, Ana, Bo] |
3 < 3 is false; loop ends |
The first two seats are both "x". Removing index 0 slid the second "x" into index 0, but i++ moved the loop on to index 1, so that "x" was never checked. The other "x" was removed because the element after it, Bo, was not an "x". A backward loop, or a loop that advances only when it keeps a seat, would remove all three.
Reread 4.9.4 Removing Elements Without Skipping if your answer differed.
Question 13 · Multiple choice
This program uses the Player class from 4.9.3. What does it print?
import java.util.ArrayList;
class Player
{
private String name;
private int points;
public Player(String playerName, int startPoints)
{
name = playerName;
points = startPoints;
}
public String getName()
{
return name;
}
public int getPoints()
{
return points;
}
public void addPoints(int amount)
{
points = points + amount;
}
}
public class TeamReset
{
public static void main(String[] args)
{
ArrayList<Player> team = new ArrayList<Player>();
team.add(new Player("Ada", 10));
team.add(new Player("Ben", 7));
for (Player p : team)
{
p = new Player(p.getName(), 0);
}
for (Player p : team)
{
p.addPoints(2);
}
for (Player p : team)
{
System.out.println(p.getName() + " " + p.getPoints());
}
}
}Ada 2andBen 2Ada 12andBen 9Ada 10andBen 7- Nothing; assigning to
pinside an enhanced for loop throws a ConcurrentModificationException.
Check your reasoning
B. The first loop (lines 38–41) assigns a new Player with 0 points to p, but p is only a copy of the reference. The list still refers to the original Ada (10) and Ben (7), and the new objects are simply forgotten. The second loop calls the mutator addPoints(2) through p, which refers to the list’s own objects, so Ada becomes 12 and Ben becomes 9.
- A is wrong: it assumes the first loop replaced the list’s players with zero-point players. Assigning to the loop variable never changes what the list holds.
- C is wrong: it misses that a mutator called through the loop variable changes the shared object.
- D is wrong: assigning to
pdoes not change the list’s size, so there is no ConcurrentModificationException. Only adding or removing elements does that.
Reread 4.9.3 Traversing with an Enhanced for Loop if you chose differently.
Question 14 · Debugging
A student wants to remove every score below 70. Since an enhanced for loop has no index, they keep their own counter. The program stops with a ConcurrentModificationException and prints nothing. Explain the error and correct the code.
import java.util.ArrayList;
public class ScoresEnhancedRemove
{
public static void main(String[] args)
{
ArrayList<Integer> scores = new ArrayList<Integer>();
scores.add(85);
scores.add(62);
scores.add(90);
scores.add(77);
int index = 0;
for (int score : scores)
{
if (score < 70)
{
scores.remove(index);
}
index++;
}
System.out.println(scores);
}
}Check your reasoning
The size changes inside an enhanced for loop. On the second pass, score is 62, so line 18 calls scores.remove(1). The counter gives the right index, but the removal still happens during an enhanced for loop. When the loop fetches the next element, the list has changed size, so Java throws a ConcurrentModificationException before line 22 can print anything. The hand-made counter does not help, because the loop itself, not the counter, is what fetches the elements.
Corrected code: replace the counter and the enhanced loop with a backward indexed loop.
import java.util.ArrayList;
public class ScoresEnhancedFixed
{
public static void main(String[] args)
{
ArrayList<Integer> scores = new ArrayList<Integer>();
scores.add(85);
scores.add(62);
scores.add(90);
scores.add(77);
for (int i = scores.size() - 1; i >= 0; i--)
{
if (scores.get(i) < 70)
{
scores.remove(i);
}
}
System.out.println(scores);
}
}This prints [85, 90, 77]. Going backward also means no score is skipped after a removal. (A while loop that advances only when a score is kept would work too.)
Reread 4.9.5 Changing the Size in an Enhanced for Loop if you missed it.
Question 15 · Short answer
A student tests the forward removal loop from 4.9.4 (for (int i = 0; i < scores.size(); i++) with scores.remove(i) when a score is below 70) on the list [62, 85, 58, 90]. It prints [85, 90], so the student says the loop is correct. Explain why this test did not reveal the bug, and describe a list that would.
Check your reasoning
Model answer: The loop skips the element that slides into index i after a removal. In [62, 85, 58, 90], the element after each low score is a score that should be kept (85 after 62, and 90 after 58), so skipping it does no harm and the output happens to be right. The bug shows up only when two scores that should be removed are next to each other, such as [85, 62, 58, 90]. There, 58 slides into index 1 after 62 is removed, the loop moves on to index 2, and 58 stays in the list: the output is [85, 58, 90].
A complete answer must:
- explain that the loop skips the element right after each removed one,
- say that in the tested list every skipped element was one that should stay, and
- give a list with two elements to remove next to each other, and what goes wrong.
Reread the Watch Out in 4.9.4 Removing Elements Without Skipping if your answer differed.
Reflection
Check each statement. If the answer is “not yet,” reread the linked subsection.
- I can traverse an ArrayList with an indexed for loop or a while loop, forward, backward, or by steps. Reread
- I can explain why an index of size() or more causes an IndexOutOfBoundsException, and fix the loop. Reread
- I can traverse an ArrayList with an enhanced for loop and say when it can and cannot change the list. Reread
- I can explain why a forward loop skips elements when removing, and remove without skipping. Reread
- I can explain why adding or removing in an enhanced for loop can cause a ConcurrentModificationException. Reread