Skip to content
HopperHopper

Course navigation

Unit 4 · Data CollectionsTopic 4.9

ArrayList Traversals

  • About 50 minutes
  • 5 sections
  • Runnable Java examples
  • Practice questions

Before you start

About 50 minutes

By the end you will be able to

  1. Traverse an ArrayList with an indexed for loop or a while loop, forward, backward, or by steps.
  2. Explain why an index outside 0 through size() minus 1 throws an IndexOutOfBoundsException, and fix the loop that causes it.
  3. Traverse an ArrayList with an enhanced for loop, and predict when the loop can and cannot change the list.
  4. Explain why removing elements in a forward indexed loop skips elements, and use a traversal that does not skip.
  5. Explain why adding or removing elements inside an enhanced for loop can throw a ConcurrentModificationException.
Section 1 of 5

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). An ArrayList tells you how many elements it holds with the method call size().
  • An array element is scores[i]. An ArrayList element is songs.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().

PlaylistIndexed.java
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: Echoes

Two 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:

PlaylistBackward.java
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
Sunrise

The 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:

PlaylistWhile.java
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:

ListLength.java
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?

  1. for (int i = 0; i < names.size(); i++)
  2. for (int i = 0; i < names.length; i++)
  3. for (int i = 0; i < names.size() - 1; i++)
  4. 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 ArrayList has no length variable, so this does not compile (cannot find symbol). That is array syntax.
  • C is wrong: the last value of i is size() - 2, so the last name is never visited. This is an off by one error.
  • D is wrong: starting at 1 skips the first name, which is at index 0.

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.

PrizeTrace.java
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 bag

The 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.

Section 2 of 5

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.

ScoresTooFar.java
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

BackwardFromSize.java
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?

ShiftedStart.java
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));
        }
    }
}
  1. It prints red, blue, green, and gold.
  2. It prints blue, green, and gold, and then stops with an IndexOutOfBoundsException.
  3. It prints blue, green, and gold, and then ends normally.
  4. 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: <= lets i reach size(), 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.

Section 3 of 5

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)
  • String is the type of each element. It must match the list’s element type, the E in ArrayList<E>.
  • song is 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

PlaylistEnhanced.java
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

TempsEnhanced.java
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 freezing

Writing 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.)

ShoutWrong.java
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:

ShoutFixed.java
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.

TeamBonus.java
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 12
Inside 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?

DoubleLoopVar.java
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?

  1. p = new Player(p.getName(), 100);
  2. p.addPoints(100);
  3. int total = p.getPoints() + 100;
  4. 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 p refer 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.

Section 4 of 5

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.

RemoveSkip.java
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

RemoveBackward.java
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

RemoveStayPut.java
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.

WordsSkip.java
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:

WordsFixed.java
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.

Section 5 of 5

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.

TaskListCrash.java
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:

TaskListFixed.java
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 finished

Adding 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

CartAddCrash.java
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:

CartAddFixed.java
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
cereal

The 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?

GuestAdd.java
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");
            }
        }
    }
}
  1. It prints Ana, Kim, Lu, and Kim's friend.
  2. It prints Ana, Kim, and Lu, and ends normally.
  3. It prints Ana and Kim, and then stops with a ConcurrentModificationException.
  4. It does not compile, because add cannot 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.

Wrap-up · 1 of 5

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

SensorCleanup.java
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: 3

If 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.

Wrap-up · 2 of 5

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)
Wrap-up · 3 of 5

Chapter summary

  • Traversing an ArrayList by index works like traversing an array, with size() in place of .length and get(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. Using size() 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 i slides later elements left, so a forward loop that always does i++ 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.
Wrap-up · 4 of 5

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));.

  1. for (int i = stops.size() - 1; i >= 0; i--)
  2. for (int i = stops.size(); i >= 0; i--)
  3. for (int i = stops.size() - 1; i > 0; i--)
  4. 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:

BackStopsA.java
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 > 0 is false when i reaches 0, so it prints only Pine and Oak. Main, at index 0, is left out.
  • D is wrong: the update counts up instead of down. It prints Pine, then i becomes 3, 3 >= 0 is true, and stops.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?

SeatsSkip.java
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?

TeamReset.java
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());
        }
    }
}
  1. Ada 2 and Ben 2
  2. Ada 12 and Ben 9
  3. Ada 10 and Ben 7
  4. Nothing; assigning to p inside 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 p does 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.

ScoresEnhancedRemove.java
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.

ScoresEnhancedFixed.java
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.

Wrap-up · 5 of 5

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