Unit 4 · 22 min

4.9 ArrayList Traversals

When you remove or add while you loop, the elements move under your feet: control the index, or walk backward.

Summary, objectives and course and exam description (CED) codes

Traversing an ArrayList means using loops (or recursion) to access all of its elements or an ordered sequence of them. Removing elements during a traversal shifts the later elements left, so you need a special technique, such as not increasing the index after a removal or traversing backward, to avoid skipping elements. An index outside 0 to size() - 1 throws an IndexOutOfBoundsException, and changing the size inside an enhanced for loop can throw a ConcurrentModificationException.

By the end of this session you can

  • 4.9.A Develop code used to traverse the elements of an ArrayList and determine the results of these traversals.

4.9.A4.9.A.14.9.A.24.9.A.34.9.A.4

Reviewed against the CED on

Know this already? Jump to the checkpoint.

1LearnThe notes, the traps and how to remember them

1. Traversing an ArrayList

Traversing an ArrayList means using iteration (loops) or recursive statements to access all of the elements, or an ordered sequence of them 4.9.A.1. The loops look like array loops with two changes: size() instead of length, and get(i) instead of [i].

ArrayList<Integer> steps = new ArrayList<Integer>();
steps.add(5200);
steps.add(8100);
steps.add(7400);

for (int i = 0; i < steps.size(); i++)          // indexed for
{
    System.out.print(steps.get(i) + " ");
}
System.out.println();

int total = 0;
for (int s : steps)                             // enhanced for (unboxing to int)
{
    total += s;
}
System.out.println("Total: " + total);

int k = steps.size() - 1;                       // while, backward
while (k >= 0)
{
    System.out.print(steps.get(k) + " ");
    k--;
}
System.out.println();

Output:

5200 8100 7400 
Total: 20700
7400 8100 5200 

To change elements, use set in an indexed loop (assigning to the enhanced for loop variable changes nothing, as with arrays):

ArrayList<String> tags = new ArrayList<String>();
tags.add("java");
tags.add("loop");

for (int i = 0; i < tags.size(); i++)
{
    tags.set(i, "#" + tags.get(i));
}
System.out.println(tags);

Output:

[#java, #loop]

Preview: a traversal can also be recursive: a method handles one element and then calls itself for the rest. Recursion is covered in topic 4.16; here is a small example to trace.

public static void printFrom(ArrayList<String> list, int index)
{
    if (index < list.size())
    {
        System.out.print(list.get(index) + " ");
        printFrom(list, index + 1);
    }
}
ArrayList<String> stops = new ArrayList<String>();
stops.add("Park");
stops.add("Museum");
stops.add("Harbor");
printFrom(stops, 0);
System.out.println();

Output:

Park Museum Harbor 

2. Removing during a traversal: the skipping bug

Deleting elements during a traversal of an ArrayList requires special techniques to avoid skipping elements 4.9.A.2. The reason: remove(i) shifts every later element one place left, so the next element moves into index i. If the loop then does i++, that element is never checked.

Goal: remove every score below 50.

ArrayList<Integer> scores = new ArrayList<Integer>();
scores.add(72);
scores.add(40);
scores.add(35);
scores.add(90);

for (int i = 0; i < scores.size(); i++)
{
    if (scores.get(i) < 50)
    {
        scores.remove(i);
    }
}
System.out.println(scores);

Output:

[72, 35, 90]

35 survived. Trace it:

i list at the start of the pass get(i) action
0 [72, 40, 35, 90] 72 keep
1 [72, 40, 35, 90] 40 remove(1); 35 shifts to index 1
2 [72, 35, 90] 90 keep (35, now at index 1, was skipped)

3. Two safe ways to remove

Technique 1: only move forward when nothing was removed.

ArrayList<Integer> scores = new ArrayList<Integer>();
scores.add(72);
scores.add(40);
scores.add(35);
scores.add(90);

int i = 0;
while (i < scores.size())
{
    if (scores.get(i) < 50)
    {
        scores.remove(i);       // the next element is now at index i: check it next
    }
    else
    {
        i++;
    }
}
System.out.println(scores);

Output:

[72, 90]

(The same idea in a for loop is to write i--; right after remove(i), so the i++ in the header cancels it out.)

Technique 2: traverse backward. Removing at index i only shifts elements after i, and a backward loop has already checked those.

ArrayList<Integer> scores = new ArrayList<Integer>();
scores.add(72);
scores.add(40);
scores.add(35);
scores.add(90);

for (int i = scores.size() - 1; i >= 0; i--)
{
    if (scores.get(i) < 50)
    {
        scores.remove(i);
    }
}
System.out.println(scores);

Output:

[72, 90]
i list at the start of the pass get(i) action
3 [72, 40, 35, 90] 90 keep
2 [72, 40, 35, 90] 35 remove(2)
1 [72, 40, 90] 40 remove(1)
0 [72, 90] 72 keep

4. Adding during a traversal

Adding has the opposite shift. add(i, obj) pushes the current element to index i + 1, so a forward loop meets it again on the next pass. Goal: put a “warm up” before every “run”.

ArrayList<String> plan = new ArrayList<String>();
plan.add("run");
plan.add("rest");
plan.add("run");

for (int i = 0; i < plan.size(); i++)
{
    if (plan.get(i).equals("run"))
    {
        plan.add(i, "warm up");
        i++;                    // skip past the "run" that just moved to i + 1
    }
}
System.out.println(plan);

Output:

[warm up, run, rest, warm up, run]

Without the extra i++, the loop would find the same “run” at index i + 1, insert again, and keep going until the program runs out of memory.

5. IndexOutOfBoundsException

Attempting to access an index value outside the range 0 to size() - 1 results in an IndexOutOfBoundsException 4.9.A.3. Two ways it happens in traversals:

ArrayList<String> names = new ArrayList<String>();
names.add("Ada");
names.add("Ben");

for (int i = 0; i <= names.size(); i++)        // <= is one pass too many
{
    System.out.println(names.get(i));
}

Output:

Ada
Ben
Exception in thread "main" java.lang.IndexOutOfBoundsException: Index 2 out of bounds for length 2

Saving the size in a variable before a loop that removes elements also fails, because the list gets shorter but the saved number does not:

ArrayList<Integer> nums = new ArrayList<Integer>();
nums.add(4);
nums.add(-1);
nums.add(6);

int n = nums.size();                            // 3, and it stays 3
for (int i = 0; i < n; i++)
{
    if (nums.get(i) < 0)
    {
        nums.remove(i);
    }
}
System.out.println(nums);

Output:

Exception in thread "main" java.lang.IndexOutOfBoundsException: Index 2 out of bounds for length 2
i list at the start of the pass get(i) action
0 [4, -1, 6] 4 keep
1 [4, -1, 6] -1 remove(1); the size is now 2
2 [4, 6] get(2) exception: n is still 3, but index 2 no longer exists

Write i < nums.size() in the condition so the loop sees the current size (and combine it with a technique from section 3).

6. ConcurrentModificationException

Changing the size of an ArrayList while traversing it with an enhanced for loop can result in a ConcurrentModificationException 4.9.A.4. So when you use an enhanced for loop on an ArrayList, you should not add or remove elements 4.9.A.4.

ArrayList<String> cart = new ArrayList<String>();
cart.add("apple");
cart.add("soda");
cart.add("bread");
cart.add("soda");

for (String item : cart)
{
    if (item.equals("soda"))
    {
        cart.remove(1);
    }
}
System.out.println(cart);

Output:

Exception in thread "main" java.util.ConcurrentModificationException

The code compiles; the exception happens when the loop tries to move to the next element after the list has changed size. Adding inside an enhanced for loop fails the same way. Use one of the indexed techniques from section 3 instead.

Loop type Read elements Replace with set Remove or add
indexed for / while yes yes yes, with a special technique
enhanced for yes no (no index) no: ConcurrentModificationException

Lock it in: memory hooks

  • Removing while traversingRemove, then stay: after a removal, do not step forward, because the next element just moved to you.
  • Backward removalWalk backward when you delete: the shifts happen behind you, never ahead.
  • Enhanced for loopFor-each is look, don't touch: no add, no remove.

Common misconceptions

  • "A normal forward for loop with remove(i) removes every match."

    Not quite. After remove(i), the next element slides into index i, and then i++ jumps past it. Two matches in a row means one is skipped.

  • "You can remove from a list inside an enhanced for loop if you are careful."

    Not quite. Changing the size of an ArrayList while traversing it with an enhanced for loop can throw a ConcurrentModificationException. Do not add or remove inside an enhanced for loop.

  • "Saving the size before the loop makes removing safe."

    Not quite. The saved size becomes too big after the first removal, so the loop reaches indexes that no longer exist and throws an IndexOutOfBoundsException. Use list.size() in the condition.

Exam traps

  • Forward loop with remove(i) and always i++: consecutive matches are skipped.
  • for (int i = 0; i <= list.size(); i++) with get(i) throws an IndexOutOfBoundsException on the last pass.
  • list.remove(x) inside for (String x : list) compiles fine and fails at run time.
  • Setting the enhanced for loop variable to a new object does not change the list; use set(i, obj) in an indexed loop.

2PracticeRecall it, then check yourself

In 30 seconds

  1. Traverse an ArrayList with for or while loops (get(i), size()), an enhanced for loop, or recursion.
  2. Deleting during a traversal needs a special technique: only increase i when nothing was removed, or traverse backward.
  3. Inserting during a forward traversal shifts the current element right, so skip past it or the loop may run forever.
  4. Indexes outside 0 to size() - 1 throw an IndexOutOfBoundsException.
  5. Changing the size inside an enhanced for loop can throw a ConcurrentModificationException, so never add or remove there.

Cover this list, say the points out loud, then take the checkpoint without scrolling back up.

Checkpoint · 5 questions

Checkpoint: ArrayList Traversals

Pick an answer to mark it. Every answer is explained, and you can try any question again. Your saved score counts first tries only, and stays in this browser.

  1. 1 Consider the following code segment. What is printed as a result of executing the code segment? 4.9.A.2
    ArrayList<String> w = new ArrayList<String>();
    w.add("cat");
    w.add("cow");
    w.add("dog");
    w.add("cub");
    w.add("emu");
    for (int i = 0; i < w.size(); i++)
    {
        if (w.get(i).substring(0, 1).equals("c"))
        {
            w.remove(i);
        }
    }
    System.out.println(w);
    Show answer

    Answer: B. When "cat" is removed at index 0, "cow" shifts into index 0, but i moves on to 1, so "cow" is skipped. Then "dog" is kept and "cub" is removed at index 2. The result is [cow, dog, emu]. "[dog, emu]" is what a correct removal would give; the other two miss that "cub" is checked and removed.

  2. 2 Consider the following code segment. What is the result of executing the code segment? 4.9.A.4
    ArrayList<Integer> vals = new ArrayList<Integer>();
    vals.add(3);
    vals.add(8);
    vals.add(5);
    vals.add(6);
    for (int v : vals)
    {
        if (v > 4)
        {
            vals.add(v - 4);
        }
    }
    System.out.println(vals);
    Show answer

    Answer: D. The code changes the size of the list (with add) while traversing it with an enhanced for loop. When the loop moves on after adding 4, a ConcurrentModificationException is thrown. It does not finish with the added values, and it does not loop forever; to add while traversing, use an indexed loop.

  3. 3 Consider the following code segment. What is the result of executing the code segment? 4.9.A.3
    ArrayList<Integer> a = new ArrayList<Integer>();
    a.add(10);
    a.add(20);
    a.add(30);
    int total = 0;
    for (int i = a.size(); i > 0; i--)
    {
        total += a.get(i);
    }
    System.out.println(total);
    Show answer

    Answer: A. The loop starts with i = a.size(), which is 3. The valid indexes are 0 to 2, so a.get(3) throws an IndexOutOfBoundsException on the first pass. "60" assumes all elements are added, "50" adds indexes 1 and 2, and a ConcurrentModificationException needs the size to change during an enhanced for loop, which does not happen here.

  4. 4 The method removeNegatives is intended to remove every negative value from list. Which of the following can replace /* missing code */ so that the method works as intended for every list? 4.9.A.2, 4.9.A.3
    public static void removeNegatives(ArrayList<Integer> list)
    {
        /* missing code */
    }
    Show answer

    Answer: B. Traversing backward means each removal only shifts elements that were already checked, so nothing is skipped. The forward loop skips the element after each removal (for example, it leaves -2 in [-1, -2, 3]); starting at list.size() throws an IndexOutOfBoundsException on the first pass; and the saved size n becomes too large after a removal, which can lead to an IndexOutOfBoundsException (for example with [-1, 5]) and also skips elements.

  5. 5 Consider the following code segment. What is printed as a result of executing the code segment? 4.9.A.1
    ArrayList<String> s = new ArrayList<String>();
    s.add("red");
    s.add("blue");
    s.add("green");
    for (int i = 0; i < s.size(); i += 2)
    {
        s.add(i, "x");
    }
    System.out.println(s);
    Show answer

    Answer: C. Each add(i, "x") inserts before the element at i and shifts it right, and i += 2 jumps past that element. With i = 0, 2 and 4, an "x" goes before red, blue and green, and the loop stops when i is 6 and the size is 6. The first two options stop too early, and the loop does end because i grows by 2 while the size grows by 1.

3PrevailLock it in for exam day

By the numbers

Name each list from memory, then reveal it to check. Each list shows how many items to recall.

Key terms: flip the cards

Read the term, say the definition out loud, then flip to check. Struggling a little to remember is what makes it stick.

  • Traversaltap to flip

    Using repetition (or recursion) to access all elements of an array or ArrayList, or an ordered sequence of them.

    Example and CED codes
  • ArrayListtap to flip

    A java.util class for a list of object references that can grow and shrink; ArrayList<E> names the element type E.

    Example and CED codes
  • ArrayList methodstap to flip

    The Java Quick Reference ArrayList methods: size, add(obj), add(index, obj), get, set and remove; add and remove at an index shift later elements.

    Example and CED codes
  • Enhanced for looptap to flip

    A loop that assigns a copy of each element of an array or ArrayList, in order, to its loop variable, without using an index.

    Example and CED codes
  • IndexOutOfBoundsExceptiontap to flip

    The run-time exception thrown when an ArrayList method gets an index outside its valid range, such as get(size()).

    Example and CED codes
  • ConcurrentModificationExceptiontap to flip

    The run-time exception that can be thrown when an ArrayList changes size while an enhanced for loop is traversing it.

    Example and CED codes
  • Recursiontap to flip

    A form of repetition in which a method calls itself; it needs at least one base case and at least one recursive call.

    Example and CED codes
  • for looptap to flip

    An iterative statement whose header has three parts: the initialization (runs once), the Boolean expression (checked before each iteration) and the update (after each iteration).

    Example and CED codes

Questions students ask

How do you remove elements from an ArrayList while looping?

Use an indexed loop and only increase the index when you do not remove, or loop backward from size() - 1 to 0. Never remove inside an enhanced for loop.

Why does my loop skip elements when I remove from an ArrayList?

remove(i) moves the next element into index i. Your i++ then moves past it without checking it. Do not increase i after a removal.

What causes a ConcurrentModificationException?

Changing the size of an ArrayList, with add or remove, while traversing it with an enhanced for loop.

Can I use an enhanced for loop with an ArrayList?

Yes, to read every element in order, for example to sum or count. Do not add or remove elements in it, and you cannot replace elements through the loop variable.

Was this page helpful?

No names are ever shown, and every public comment is checked by us first. Please do not include personal details. To get a reply, email [email protected].

↑ Top

Enlarged view of the selected figure