Learn Java

Lesson 7Phase 1: First steps

ArrayList: a list that can grow

When an array's fixed size gets in the way: adding, removing and looping over an ArrayList.

By Updated 45 min

  • ArrayList
  • add
  • get
  • set
  • remove
  • size

An array holds a row of values, and you choose its length the moment you create it. That works when you know the count ahead of time. Often you do not: test scores come in one student at a time, songs get added to a playlist, items leave a shopping basket. An ArrayList is a list that grows when you add to it and gets shorter when you take something out. This lesson shows you how to use one, and what it does on the inside.

By the end of this lesson
  • Create an ArrayList, add values to it, print it, and read values back with get.
  • Change a list with set, add at a position and remove, and say which remove runs on a list of numbers.
  • Ask a list about itself with size, isEmpty, contains and indexOf.
  • Walk a list with a counting loop and a for-each loop, and remove items while looping without a crash.
  • Explain how an ArrayList grows: a hidden array with spare room, swapped for a bigger copy when it fills up.
  • Choose between an array, an ArrayList and a fixed list made with List.of.
From before
This lesson builds on one-dimensional arrays: a row of values, indexes that start at 0, .length, and the counting for loop and for-each loop that walk the row. You also need i++ and i-- from loops. ArrayList is a ready-made tool from Java's library. You will learn to use it here, and Phase 2 explains the idea it is built on.

When an array is the wrong size#

Say you are collecting test scores as students hand in their papers, and you do not know how many students will turn up. An array makes you pick a length on day one. Pick 3, and the fourth paper has nowhere to go.

An array cannot grow, so the only way to fit one more score is to build a bigger array, copy every old score into it, and then store the new one. Step through this and watch the copy happen.

FullArray.java
1public class Main {
2 public static void main(String[] args) {
3 int[] scores = {90, 75, 88};
4
5 // A fourth score arrives, but scores has only 3 slots.
6 // Build a bigger array and copy the old scores across.
7 int[] bigger = new int[4];
8 for (int i = 0; i < scores.length; i++) {
9 bigger[i] = scores[i];
10 }
11 bigger[3] = 64;
12 scores = bigger;
13
14 System.out.println("Scores stored: " + scores.length);
15 System.out.println("Newest score: " + scores[3]);
16 }
17}
Prints
›Scores stored: 4
›Newest score: 64

It prints Scores stored: 4 and Newest score: 64. The last step, scores = bigger;, points scoresat the new array so the rest of the program uses it. That is a new array, a loop and a reassignment for every score that arrives, and removing a score would need its own loop to shift the later values left. This chore is so common that Java's library does it for you, in a ready-made tool called ArrayList.

Your first ArrayList#

Here is the same job done with an ArrayList. There is no length to choose. You start with an empty list and add each score when it arrives.

Scores.java
1import java.util.ArrayList;
2
3public class Main {
4 public static void main(String[] args) {
5 ArrayList<Integer> scores = new ArrayList<>();
6
7 scores.add(90);
8 scores.add(75);
9 scores.add(88);
10 scores.add(64);
11
12 System.out.println(scores);
13 System.out.println("Number of scores: " + scores.size());
14 System.out.println("First score: " + scores.get(0));
15 }
16}
Prints
›[90, 75, 88, 64]
›Number of scores: 4
›First score: 90

It prints [90, 75, 88, 64], then Number of scores: 4, then First score: 90. Step through it and watch the memory view under the code. The list is drawn as a row of numbered cells that gains a cell with each add, and the narration says what each line did. The faint empty cells after the values are spare room, and the small caption under the row reads size 4, capacity 10. Those spare cells get their own section later on.

Reading the recipe

Three lines in that program are new. Take them one at a time.

  • import java.util.ArrayList; sits above the class. ArrayList is not built into the language the way int is. It comes from Java's standard library, the large set of ready-made code that ships with the JDK. The library is sorted into named groups called packages, and ArrayList lives in the one called java.util. The import line tells the compiler where to find it. Leave it out and the compiler stops with cannot find symbol.
  • ArrayList<Integer> scores = new ArrayList<>(); declares a variable and gives it a brand-new, empty list. The type is ArrayList<Integer>, an ArrayList that holds Integer values (a little further down you will see why it says Integer and not int). The angle brackets say what goes inside, like a label on a storage box. The word new builds the list. The angle brackets on the right are empty, <>, which tells Java to copy the type from the left so you do not write it twice. People call that empty pair the diamond.
  • scores.add(90);asks the list to do something. Read it as “scores, add 90”. The actions a list knows how to do are called its methods, and the dot picks one of them. add puts the value in the parentheses at the end of the list.

Printing a list shows its contents in square brackets, separated by commas. Printing an array does not: System.out.println on an int[] gives something like [I@1b6d3586, a type code and a number instead of the values. scores.size() answers how many values the list holds right now, and scores.get(0) fetches the value at index 0. Indexes count from 0, exactly as they do in an array.

Key idea

An ArrayList is a list that grows. You create it empty with new, say what it holds in the angle brackets, and ask it to add, get or remove values with a dot.

Objects get a phase of their own
The list is your first object: a value that holds data and also knows how to do things with it. A plain int just sits there being a number. scores holds its numbers and knows how to add, find and remove them when you ask with a dot. Phase 2 explains objects properly, starting with why programs are built from objects. Until then, treat the declaration line as a recipe and copy its shape.
Coming from Python or JavaScript?
An ArrayList does the job of a Python list or a JavaScript array: it grows as you append to it. Two differences stand out. Every value must have the type named in the angle brackets, and you read and write values through methods such as get and set, because square brackets only work on Java arrays.

Why Integer and not int

Scores are whole numbers, so you might expect to write ArrayList<int>. The compiler refuses:

Terminal
read along
1$ javac Main.java
2Main.java:5: error: unexpected type
3 ArrayList<int> ages = new ArrayList<>();
4 ^
5 required: reference
6 found: int
71 error

The angle brackets only accept object types. int, double, boolean and char are primitive types: plain values, not objects. “Required: reference” is the compiler's way of asking for an object type. So each primitive type has an object partner made for this job, called a wrapper class:

Primitive typeWrapper classA list of them
intIntegerArrayList<Integer>
doubleDoubleArrayList<Double>
booleanBooleanArrayList<Boolean>
charCharacterArrayList<Character>
longLongArrayList<Long>
String is already an object type, so ArrayList<String> needs no wrapper.

You rarely have to do the wrapping yourself. When you write scores.add(90), Java wraps the int 90 in an Integer for you, which is called autoboxing. When you read a value back into an int variable, as in int first = scores.get(0);, Java unwraps it again, which is called unboxing.

Comparing two values from a list
== on two Integer values does not compare the numbers inside. It asks whether both are the same object. For small numbers that happens to give the answer you expect, and for bigger ones it can say false about two equal scores. Copy the values into int variables first, int a = scores.get(0); and int b = scores.get(1);, and then a == b compares the numbers. Wrapper classes and autoboxing explains why, along with the other traps that come with wrapping.
Quick check
Which of these lines compiles?

Reading and changing items#

Four methods do most of the everyday work. Each takes an index, counting from 0 like an array.

  • get(i) hands back the value at index i.
  • set(i, value) replaces the value at index i. The list stays the same size.
  • add(i, value) inserts a value at index i. Everything from i onward moves one place right to make room.
  • remove(i) takes out the value at index i. Everything after it moves one place left to close the gap.

Step through this playlist and keep an eye on the memory view at each change. After an insert, every later song sits one cell further right, and after a remove, one cell further left.

Playlist.java
1import java.util.ArrayList;
2
3public class Main {
4 public static void main(String[] args) {
5 ArrayList<String> playlist = new ArrayList<>();
6 playlist.add("Intro");
7 playlist.add("Waves");
8 playlist.add("Echo");
9
10 playlist.add(1, "Sunrise");
11 System.out.println(playlist);
12
13 playlist.set(2, "Tides");
14 System.out.println(playlist);
15
16 playlist.remove(0);
17 System.out.println(playlist);
18
19 System.out.println("Now playing: " + playlist.get(0));
20 }
21}
Prints
›[Intro, Sunrise, Waves, Echo]
›[Intro, Sunrise, Tides, Echo]
›[Sunrise, Tides, Echo]
›Now playing: Sunrise

After playlist.add(1, "Sunrise") the list prints as [Intro, Sunrise, Waves, Echo]: Sunrise slid in at index 1 and pushed Waves and Echo one place right. playlist.set(2, "Tides") overwrote Waves, so the list still holds four songs: [Intro, Sunrise, Tides, Echo]. playlist.remove(0) took Intro out and everything shifted left, giving [Sunrise, Tides, Echo], so the last line prints Now playing: Sunrise.

set(1, x)
add(1, x)
Replaces what is at index 1.
Inserts a new value at index 1.
The size stays the same.
The size goes up by one.
Nothing moves.
Everything from index 1 onward moves one place right.
The index must already exist: 0 to size() - 1.
The index can be 0 to size(). Using size() itself means “at the end”.

Asking for an index that is not there

The valid indexes of a list run from 0 to size() - 1, the same rule as an array. Ask for anything outside that range and the program stops.

Bounds.java
1import java.util.ArrayList;
2
3public class Main {
4 public static void main(String[] args) {
5 ArrayList<String> playlist = new ArrayList<>();
6 playlist.add("Sunrise");
7 playlist.add("Tides");
8 playlist.add("Echo");
9
10 System.out.println("Last song: " + playlist.get(2));
11 System.out.println("One more: " + playlist.get(3));
12 }
13}
Prints
›Last song: Echo
!Exception in thread "main" java.lang.IndexOutOfBoundsException: Index 3 out of bounds for length 3
! at Main.main(Main.java:11)
Stops on line 11

IndexOutOfBoundsException: Index 3 out of bounds for length 3

A list position must be at least 0 and less than size().

The program prints Last song: Echo and then stops on the second get. In a terminal, Java reports Exception in thread "main" java.lang.IndexOutOfBoundsException: Index 3 out of bounds for length 3, followed by a few lines starting with at that say where it happened.

Read it the way you learned for arrays. The first number is the index you asked for, and the second is the size of the list (the message calls it length). When the two are equal, you asked for one past the end. An array reports an ArrayIndexOutOfBoundsException, and a list reports the more general IndexOutOfBoundsException, with the same meaning. Inserting past the end fails the same way, worded a little differently: add(9, x) on a two-item list reports Index: 9, Size: 2. Reading the rest of the message is part of reading errors and debugging.

Quick check
songs holds 4 songs. Which line stops the program with an IndexOutOfBoundsException?

Removing by position or by value#

remove comes in two forms. Give it an index and it removes whatever sits at that position. Give it a value and it searches for that value and removes the first match.

Basket.java
1import java.util.ArrayList;
2
3public class Main {
4 public static void main(String[] args) {
5 ArrayList<String> basket = new ArrayList<>();
6 basket.add("apple");
7 basket.add("bread");
8 basket.add("apple");
9 basket.add("cheese");
10
11 basket.remove("apple");
12 System.out.println(basket);
13
14 basket.remove(2);
15 System.out.println(basket);
16
17 boolean removed = basket.remove("jam");
18 System.out.println("Removed jam? " + removed);
19 }
20}
Prints
›[bread, apple, cheese]
›[bread, apple]
›Removed jam? false

basket.remove("apple") took out only the first apple, leaving [bread, apple, cheese]. basket.remove(2) took out whatever was at index 2, the cheese, leaving [bread, apple]. Removing a value that is not in the list changes nothing, and remove answers false so you can tell: the last line prints Removed jam? false.

With a list of strings, Java tells the two forms apart easily: a number means a position and text means a value. A list of numbers is where it goes wrong. A hotel keeps a list of rooms that still need cleaning, and room 3 has just been cleaned. Predict what this prints before you run it.

Predict the outputRooms.java
1import java.util.ArrayList;
2
3public class Main {
4 public static void main(String[] args) {
5 ArrayList<Integer> rooms = new ArrayList<>();
6 rooms.add(12);
7 rooms.add(7);
8 rooms.add(3);
9 rooms.add(9);
10
11 // Room 3 is clean now, so take it off the list
12 rooms.remove(3);
13 System.out.println(rooms);
14 }
15}

The list holds rooms 12, 7, 3 and 9. What does the program print?

It prints [12, 7, 3]. Room 9 vanished and room 3 is still waiting. The call rooms.remove(3) removed index 3, the fourth room in the list, which happened to be room 9.

Why did Java pick the position? ArrayList has two methods named remove: one takes an int index, and the other takes an object to search for. (Two methods sharing a name is called overloading, and you will write your own in the methods lesson.) Your argument, 3, is an int. When Java chooses between methods with the same name, it first looks for one that takes the argument as it is, without wrapping it. remove(int) takes an int as it is, so it wins, and Java never tries wrapping 3 into an Integer.

To remove the value, hand remove an Integer object instead of a plain int. Integer.valueOf(3) gives you one:

Rooms.java
1import java.util.ArrayList;
2
3public class Main {
4 public static void main(String[] args) {
5 ArrayList<Integer> rooms = new ArrayList<>();
6 rooms.add(12);
7 rooms.add(7);
8 rooms.add(3);
9 rooms.add(9);
10
11 rooms.remove(Integer.valueOf(3));
12 System.out.println(rooms);
13 }
14}
Prints
›[12, 7, 9]

Now it prints [12, 7, 9]. An Integer fits remove(Object) as it is, while remove(int) would need it unwrapped first, so this time Java picks the search, finds the value 3 and removes it.

The quiet version of this bug is the dangerous one
If the room had been number 30, rooms.remove(30) would stop the program with an IndexOutOfBoundsException and you would find the mistake at once. With 3, which is a valid index, it removes the wrong room and carries on without a word. Whenever you call remove on a list of numbers, stop and ask yourself: do I mean a position or a value?
Key idea

remove(i) with an int removes a position. remove(Integer.valueOf(x)) removes a value. On a list of numbers, write the form that says what you mean.

Asking a list questions#

Some methods change nothing. They only answer a question about the list.

Shopping.java
1import java.util.ArrayList;
2
3public class Main {
4 public static void main(String[] args) {
5 ArrayList<String> shopping = new ArrayList<>();
6 System.out.println("Empty at the start? " + shopping.isEmpty());
7
8 shopping.add("milk");
9 shopping.add("bread");
10 shopping.add("eggs");
11
12 System.out.println("Items: " + shopping.size());
13 System.out.println("Need eggs? " + shopping.contains("eggs"));
14 System.out.println("Need jam? " + shopping.contains("jam"));
15 System.out.println("Where is bread? " + shopping.indexOf("bread"));
16 System.out.println("Where is jam? " + shopping.indexOf("jam"));
17
18 shopping.clear();
19 System.out.println("After clear: " + shopping + ", empty? " + shopping.isEmpty());
20 }
21}
Prints
›Empty at the start? true
›Items: 3
›Need eggs? true
›Need jam? false
›Where is bread? 1
›Where is jam? -1
›After clear: [], empty? true
  • isEmpty() answers true when the list holds nothing. It means the same as size() == 0 and reads better.
  • size() is the number of values in the list right now: Items: 3.
  • contains(x) answers whether the value is anywhere in the list: true for eggs, false for jam.
  • indexOf(x) gives the index of the first match, here 1 for bread, or -1 when there is no match. A real index is never negative, so -1 can safely mean “not found”.
  • clear() removes everything at once. The list prints as [] afterwards.

You now know three ways to ask “how many?”, one for each kind of thing. Expect to mix them up a few times; the compiler tells you when you do.

You haveYou writeWhy it looks like that
An arrayscores.lengthlength is a field of the array, so there are no parentheses.
A Stringname.length()length() is a method of the string.
An ArrayListscores.size()size() is a method of the list, and its answer changes as the list grows and shrinks.

Fixed lists with List.of#

Typing one add per line gets tedious when you already know the values. List.of, added in Java 9, builds a list from its values in a single line. The list it builds can never change, though, and the program below shows what happens when you try.

Days.java
1import java.util.List;
2
3public class Main {
4 public static void main(String[] args) {
5 List<String> days = List.of("Mon", "Tue", "Wed", "Thu", "Fri");
6 System.out.println(days);
7 System.out.println(days.size() + " school days, starting with " + days.get(0));
8
9 days.add("Sat");
10 System.out.println("This line never runs");
11 }
12}
Prints
›[Mon, Tue, Wed, Thu, Fri]
›5 school days, starting with Mon
!Exception in thread "main" java.lang.UnsupportedOperationException
! at Main.main(Main.java:9)
Stops on line 9

UnsupportedOperationException

This collection cannot be changed. List.of and similar methods make read-only lists.

The first two lines print [Mon, Tue, Wed, Thu, Fri] and 5 school days, starting with Mon. Then days.add("Sat") stops the program with an UnsupportedOperationException. A list made by List.of is unmodifiable: add, set, remove and clear all throw this exception, while reading with get, size and contains works as usual. Notice that the program compiled. The compiler does not know this list is fixed, so the mistake only shows up when the line runs.

The variable's type is List<String>, without “Array” in front. List is the general name for any list. An ArrayList is one kind of List, the fixed list that List.of returns is another kind, and a List variable can hold either. To use the name, import it with import java.util.List;. You will often see real code declare List<Integer> scores = new ArrayList<>(); for this reason. Everything on this page works the same with that declaration, and interfaces explains why programmers like to write it that way.

When you want a list that starts with some values and can still change, hand the fixed list to new ArrayList<>(...). It copies the values into a fresh ArrayList that you are free to change:

Week.java
1import java.util.ArrayList;
2import java.util.List;
3
4public class Main {
5 public static void main(String[] args) {
6 ArrayList<String> week = new ArrayList<>(List.of("Mon", "Tue", "Wed", "Thu", "Fri"));
7 week.add("Sat");
8 System.out.println(week);
9 }
10}
Prints
›[Mon, Tue, Wed, Thu, Fri, Sat]

That prints [Mon, Tue, Wed, Thu, Fri, Sat]. The rest of this lesson uses this shortcut to keep the examples short.

Walking through a list#

Both loops from the arrays lesson work on a list. The counting loop runs an index from 0 up to size() - 1 and reads each value with get(i). The for-each loop hands you each value in turn and hides the index.

Average.java
1import java.util.ArrayList;
2import java.util.List;
3
4public class Main {
5 public static void main(String[] args) {
6 ArrayList<Integer> scores = new ArrayList<>(List.of(90, 75, 88));
7
8 for (int i = 0; i < scores.size(); i++) {
9 System.out.println("Student " + (i + 1) + ": " + scores.get(i));
10 }
11
12 int total = 0;
13 for (int score : scores) {
14 total += score;
15 }
16 System.out.println("Average: " + (double) total / scores.size());
17 }
18}
Prints
›Student 1: 90
›Student 2: 75
›Student 3: 88
›Average: 84.33333333333333

The counting loop prints Student 1: 90, Student 2: 75 and Student 3: 88. It uses scores.size() where an array loop used .length, and scores.get(i) where it used scores[i]. Reach for it when you need the position, here to number the students.

The for-each loop is shorter when you only need the values. Its loop variable is a plain int, and Java unboxes each Integer on the way out. The total is 253, and the last line prints Average: 84.33333333333333. The (double) cast stops integer division from dropping the fraction, as in the arrays lesson.

Put the lines in orderHighest.java
Put the lines in order so the program finds the highest temperature in the list and prints Highest: 24.

Tap a line, then tap where it should go. Or use the arrows.

Removing items while you loop

Say you want to drop every score below 50. The obvious attempt removes them inside a for-each loop. Run it.

RemoveInLoop.java
1import java.util.ArrayList;
2import java.util.List;
3
4public class Main {
5 public static void main(String[] args) {
6 ArrayList<Integer> scores = new ArrayList<>(List.of(72, 45, 38, 90));
7
8 for (int score : scores) {
9 if (score < 50) {
10 scores.remove(Integer.valueOf(score));
11 }
12 }
13 System.out.println(scores);
14 }
15}
Prints
!Exception in thread "main" java.util.ConcurrentModificationException
! at Main.main(Main.java:8)
Stops on line 8

ConcurrentModificationException

The collection changed while a loop was walking through it.

Java stops with a ConcurrentModificationException, and it points at the for line, not at the removeline. A for-each loop keeps a bookmark in the list as it walks. Removing 45 shifted the later values left behind the loop's back, so when the loop went to fetch the next value, it saw that the list had changed under it and stopped rather than guess. “Concurrent modification” here means “changed while being walked”. Adding inside a for-each loop fails the same way.

A counting loop does not crash, but it has a quieter bug. Predict what this prints.

Predict the outputSkip.java
1import java.util.ArrayList;
2import java.util.List;
3
4public class Main {
5 public static void main(String[] args) {
6 ArrayList<Integer> scores = new ArrayList<>(List.of(72, 45, 38, 90));
7
8 for (int i = 0; i < scores.size(); i++) {
9 if (scores.get(i) < 50) {
10 scores.remove(i);
11 }
12 }
13 System.out.println(scores);
14 }
15}

The loop removes every score below 50 from [72, 45, 38, 90]. What does the program print?

It prints [72, 38, 90]. When i was 1, the loop removed 45, and 38 slid left into index 1. Then i++ moved on to index 2, so the loop never looked at 38.

That leaves two safe ways. The first is to walk the list backwards. Removing a value shifts only the values after it, and going backwards you have already checked all of those.

Backwards.java
1import java.util.ArrayList;
2import java.util.List;
3
4public class Main {
5 public static void main(String[] args) {
6 ArrayList<Integer> scores = new ArrayList<>(List.of(72, 45, 38, 90));
7
8 for (int i = scores.size() - 1; i >= 0; i--) {
9 if (scores.get(i) < 50) {
10 scores.remove(i);
11 }
12 }
13 System.out.println(scores);
14 }
15}
Prints
›[72, 90]

It prints [72, 90]. This time removing by position is exactly what you want: i is an index, so scores.remove(i) removes the value at that position. The second safe way is removeIf, which walks the list and does the removing for you:

RemoveIf.java
1import java.util.ArrayList;
2import java.util.List;
3
4public class Main {
5 public static void main(String[] args) {
6 ArrayList<Integer> scores = new ArrayList<>(List.of(72, 45, 38, 90));
7
8 scores.removeIf(score -> score < 50);
9 System.out.println(scores);
10 }
11}
Prints
›[72, 90]

It prints [72, 90] too. Read score -> score < 50as “given a score, remove it if it is below 50”. That small rule is called a lambda. For now, copy the shape and change the condition; enums and lambdas explains how it works.

Key idea

Never add to or remove from a list while a for-each loop is walking it. Walk it backwards with an index, or let removeIf do the removing.

Loops over collections, in depth
The bookmark a for-each loop keeps is an object called an iterator, and it has a safe remove of its own. The Collections Framework covers iterators properly, along with the other kinds of collection.
ExercisePassingScores.java
Remove every score below 50 from the list, without a crash and without skipping any, so the program prints only the passing scores in their original order.

Inside an ArrayList: the backing array#

Remember the chore from the start of the lesson: build a bigger array, copy everything across, then store the new value. An ArrayList does exactly that, out of sight. Inside every ArrayList is an ordinary array called its backing array, and the list keeps its values in the first slots of it.

The backing array is usually longer than the list. Its length is the list's capacity, while the number of values actually stored is the size. When you add and a slot is free, the value simply goes into the next free slot. When every slot is taken, the add does three things:

  1. 1It makes a new array half as big again: the old length plus half of it, rounded down. 10 slots become 15, 15 become 22, 22 become 33.
  2. 2It copies every value from the old array into the new one, in order.
  3. 3It stores the new value in the next free slot, and from then on uses only the new array. Nothing points at the old array any more, so Java frees its memory automatically.

A list made with new ArrayList<>() starts with no backing array at all, and the first add makes one with 10 slots. Press the buttons and watch the slots fill. Add values until the array is full, then add one more.

Inside an ArrayListsize 0 · capacity 0
ArrayList<Integer> scores = new ArrayList<>();

No backing array yet

An empty list: size 0, and no backing array yet, so capacity 0. Press Add one.

Arrays made
0
Values copied so far
0
Capacity so far
0

Now watch the real thing. This program adds 16 ticket numbers to an empty list. Step through it and read the narration at the 1st, 11th and 16th add: the list makes its first array, then copies its 10 values into a 15-slot array, then its 15 values into a 22-slot array.

Tickets.java
1import java.util.ArrayList;
2
3public class Main {
4 public static void main(String[] args) {
5 ArrayList<Integer> tickets = new ArrayList<>();
6
7 for (int n = 1; n <= 16; n++) {
8 tickets.add(100 + n);
9 }
10
11 System.out.println(tickets);
12 System.out.println("size: " + tickets.size());
13 }
14}
Prints
›[101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116]
›size: 16

It prints the 16 tickets from 101 to 116 in square brackets, then size: 16. The memory view ends with 16 values and 6 spare slots: size 16, capacity 22.

Removing never makes the backing array smaller. Remove ten values and there are simply ten more spare slots, and even clear() keeps the same array ready for the next round of adds. A list copied from List.of, like the ones earlier in this lesson, starts with exactly as many slots as values, which is why those lists started with no spare slots in the memory view.

Key idea

An ArrayList is an array with spare room. When the room runs out, it makes a new array half as big again, copies everything across and carries on.

Quick check
A list made with new ArrayList<>() holds 10 values, and you add an 11th. What happens inside?

Array or ArrayList?#

Both hold a row of values that you reach by index. The table compares them task by task.

TaskArrayArrayList
Create oneint[] a = new int[5];ArrayList<Integer> list = new ArrayList<>();
SizeFixed when you create itGrows and shrinks as you add and remove
What it holdsPrimitives such as int, or objectsObjects only, so Integer instead of int
Read a valuea[i]list.get(i)
Change a valuea[i] = x;list.set(i, x);
Add at the endNo room: build a bigger array and copylist.add(x);
Insert or remove in the middleShift the other values yourself with a looplist.add(i, x); and list.remove(i); shift for you
How manya.lengthlist.size()
Print itShows something like [I@1b6d3586Shows [1, 2, 3]
Bad indexArrayIndexOutOfBoundsExceptionIndexOutOfBoundsException

Reach for an array when the count is known and fixed: the rainfall for each of the 12 months, the 9 squares of a tic-tac-toe board, the grids in the next lesson. Reach for an ArrayList when values arrive or leave while the program runs: a shopping basket, a waiting list, the results of a search. For a set of values that should never change, List.of gives you a list that cannot be changed by accident.

An array of numbers also takes less memory, because each number sits directly in its slot instead of inside its own Integer object. That matters for millions of numbers, and not for a class list.

ExerciseEvens.java
You cannot know in advance how many of these sensor readings are even, which makes this a job for a list. Add every even reading to evens, in order. The program then prints the list and how many there were.
Next: rows and columns
Next you line arrays up into a grid with two-dimensional arrays. A board with a fixed number of rows and columns is exactly the kind of job an array does well.

Try it

Answer each one in your head or on paper first, then open the answer.

1
Work out what list holds after each line, then what the program prints.
2
Why does ArrayList<double> prices = new ArrayList<>(); not compile, and what do you write instead?
3
shopping holds [milk, bread, eggs]. What do shopping.indexOf("eggs"), shopping.indexOf("jam") and shopping.contains("bread") give?
4
This program adds 23 values to a new ArrayList. Before you run it, work out the capacity at the end, how many backing arrays were made, and how many values were copied in total. Then step through it and check the memory view.
5
Why does walking a list backwards make it safe to remove by index inside the loop?
6
For each job, choose an array, an ArrayList or List.of: the names of the twelve months, the songs in a playlist the user keeps editing, the squares of a chessboard.

Practice

Work through these on your own. Each one builds on this page.

1
Write a to-do list program. Start with an empty ArrayList<String> called todo and add four tasks. Remove the second task when it is done, insert an urgent task at the front, then print every task numbered from 1, one per line, like 1. Call the dentist.
2
Dice rolls are stored in new ArrayList<>(List.of(3, 6, 3, 1, 6, 6, 2)). Build a second list that holds each value once, in the order it first appears, and print it. You should get [3, 6, 1, 2].
3
Write a program that builds a new list holding the values of another list in reverse order, using a counting loop that runs backwards and add. Leave the original list unchanged, and print both lists to prove it.
4
Without running anything, work out the capacity of a list made with new ArrayList<>() after 30 adds, after 34 adds and after 50 adds. Then check your answers with the Inside an ArrayList demo above.

Further reading#

Short guides on this topic

Quick answers to what people search for about it, with code to run.

Spotted a mistake, or a part that did not make sense? Report it by email