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 BasithUpdated 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.
- Create an ArrayList, add values to it, print it, and read values back with
get. - Change a list with
set,addat a position andremove, and say whichremoveruns on a list of numbers. - Ask a list about itself with
size,isEmpty,containsandindexOf. - 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.
.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.
public class Main { public static void main(String[] args) { int[] scores = {90, 75, 88}; // A fourth score arrives, but scores has only 3 slots. // Build a bigger array and copy the old scores across. int[] bigger = new int[4]; for (int i = 0; i < scores.length; i++) { bigger[i] = scores[i]; } bigger[3] = 64; scores = bigger; System.out.println("Scores stored: " + scores.length); System.out.println("Newest score: " + scores[3]); }}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.
import java.util.ArrayList; public class Main { public static void main(String[] args) { ArrayList<Integer> scores = new ArrayList<>(); scores.add(90); scores.add(75); scores.add(88); scores.add(64); System.out.println(scores); System.out.println("Number of scores: " + scores.size()); System.out.println("First score: " + scores.get(0)); }}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 wayintis. 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 calledjava.util. The import line tells the compiler where to find it. Leave it out and the compiler stops withcannot find symbol.ArrayList<Integer> scores = new ArrayList<>();declares a variable and gives it a brand-new, empty list. The type isArrayList<Integer>, an ArrayList that holdsIntegervalues (a little further down you will see why it saysIntegerand notint). The angle brackets say what goes inside, like a label on a storage box. The wordnewbuilds 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.addputs 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 ideaAn ArrayList is a list that grows. You create it empty with
new, say what it holds in the angle brackets, and ask it toadd,getorremovevalues with a dot.
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.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:
$ javac Main.javaMain.java:5: error: unexpected type ArrayList<int> ages = new ArrayList<>(); ^ required: reference found: int1 errorThe 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 type | Wrapper class | A list of them |
|---|---|---|
int | Integer | ArrayList<Integer> |
double | Double | ArrayList<Double> |
boolean | Boolean | ArrayList<Boolean> |
char | Character | ArrayList<Character> |
long | Long | ArrayList<Long> |
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.
== 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.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 indexi.set(i, value)replaces the value at indexi. The list stays the same size.add(i, value)inserts a value at indexi. Everything fromionward moves one place right to make room.remove(i)takes out the value at indexi. 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.
import java.util.ArrayList; public class Main { public static void main(String[] args) { ArrayList<String> playlist = new ArrayList<>(); playlist.add("Intro"); playlist.add("Waves"); playlist.add("Echo"); playlist.add(1, "Sunrise"); System.out.println(playlist); playlist.set(2, "Tides"); System.out.println(playlist); playlist.remove(0); System.out.println(playlist); System.out.println("Now playing: " + playlist.get(0)); }}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.
size() - 1.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.
import java.util.ArrayList; public class Main { public static void main(String[] args) { ArrayList<String> playlist = new ArrayList<>(); playlist.add("Sunrise"); playlist.add("Tides"); playlist.add("Echo"); System.out.println("Last song: " + playlist.get(2)); System.out.println("One more: " + playlist.get(3)); }}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.
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.
import java.util.ArrayList; public class Main { public static void main(String[] args) { ArrayList<String> basket = new ArrayList<>(); basket.add("apple"); basket.add("bread"); basket.add("apple"); basket.add("cheese"); basket.remove("apple"); System.out.println(basket); basket.remove(2); System.out.println(basket); boolean removed = basket.remove("jam"); System.out.println("Removed jam? " + removed); }}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.
import java.util.ArrayList; public class Main { public static void main(String[] args) { ArrayList<Integer> rooms = new ArrayList<>(); rooms.add(12); rooms.add(7); rooms.add(3); rooms.add(9); // Room 3 is clean now, so take it off the list rooms.remove(3); System.out.println(rooms); }}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:
import java.util.ArrayList; public class Main { public static void main(String[] args) { ArrayList<Integer> rooms = new ArrayList<>(); rooms.add(12); rooms.add(7); rooms.add(3); rooms.add(9); rooms.remove(Integer.valueOf(3)); System.out.println(rooms); }}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.
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 anintremoves 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.
import java.util.ArrayList; public class Main { public static void main(String[] args) { ArrayList<String> shopping = new ArrayList<>(); System.out.println("Empty at the start? " + shopping.isEmpty()); shopping.add("milk"); shopping.add("bread"); shopping.add("eggs"); System.out.println("Items: " + shopping.size()); System.out.println("Need eggs? " + shopping.contains("eggs")); System.out.println("Need jam? " + shopping.contains("jam")); System.out.println("Where is bread? " + shopping.indexOf("bread")); System.out.println("Where is jam? " + shopping.indexOf("jam")); shopping.clear(); System.out.println("After clear: " + shopping + ", empty? " + shopping.isEmpty()); }}isEmpty()answerstruewhen the list holds nothing. It means the same assize() == 0and 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:truefor eggs,falsefor jam.indexOf(x)gives the index of the first match, here1for bread, or-1when there is no match. A real index is never negative, so-1can 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 have | You write | Why it looks like that |
|---|---|---|
| An array | scores.length | length is a field of the array, so there are no parentheses. |
| A String | name.length() | length() is a method of the string. |
| An ArrayList | scores.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.
import java.util.List; public class Main { public static void main(String[] args) { List<String> days = List.of("Mon", "Tue", "Wed", "Thu", "Fri"); System.out.println(days); System.out.println(days.size() + " school days, starting with " + days.get(0)); days.add("Sat"); System.out.println("This line never runs"); }}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:
import java.util.ArrayList;import java.util.List; public class Main { public static void main(String[] args) { ArrayList<String> week = new ArrayList<>(List.of("Mon", "Tue", "Wed", "Thu", "Fri")); week.add("Sat"); System.out.println(week); }}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.
import java.util.ArrayList;import java.util.List; public class Main { public static void main(String[] args) { ArrayList<Integer> scores = new ArrayList<>(List.of(90, 75, 88)); for (int i = 0; i < scores.size(); i++) { System.out.println("Student " + (i + 1) + ": " + scores.get(i)); } int total = 0; for (int score : scores) { total += score; } System.out.println("Average: " + (double) total / scores.size()); }}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.
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.
import java.util.ArrayList;import java.util.List; public class Main { public static void main(String[] args) { ArrayList<Integer> scores = new ArrayList<>(List.of(72, 45, 38, 90)); for (int score : scores) { if (score < 50) { scores.remove(Integer.valueOf(score)); } } System.out.println(scores); }}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.
import java.util.ArrayList;import java.util.List; public class Main { public static void main(String[] args) { ArrayList<Integer> scores = new ArrayList<>(List.of(72, 45, 38, 90)); for (int i = 0; i < scores.size(); i++) { if (scores.get(i) < 50) { scores.remove(i); } } System.out.println(scores); }}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.
import java.util.ArrayList;import java.util.List; public class Main { public static void main(String[] args) { ArrayList<Integer> scores = new ArrayList<>(List.of(72, 45, 38, 90)); for (int i = scores.size() - 1; i >= 0; i--) { if (scores.get(i) < 50) { scores.remove(i); } } System.out.println(scores); }}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:
import java.util.ArrayList;import java.util.List; public class Main { public static void main(String[] args) { ArrayList<Integer> scores = new ArrayList<>(List.of(72, 45, 38, 90)); scores.removeIf(score -> score < 50); System.out.println(scores); }}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 ideaNever add to or remove from a list while a for-each loop is walking it. Walk it backwards with an index, or let
removeIfdo the removing.
remove of its own. The Collections Framework covers iterators properly, along with the other kinds of collection.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:
- 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.
- 2It copies every value from the old array into the new one, in order.
- 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.
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.
import java.util.ArrayList; public class Main { public static void main(String[] args) { ArrayList<Integer> tickets = new ArrayList<>(); for (int n = 1; n <= 16; n++) { tickets.add(100 + n); } System.out.println(tickets); System.out.println("size: " + tickets.size()); }}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 ideaAn 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.
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.
| Task | Array | ArrayList |
|---|---|---|
| Create one | int[] a = new int[5]; | ArrayList<Integer> list = new ArrayList<>(); |
| Size | Fixed when you create it | Grows and shrinks as you add and remove |
| What it holds | Primitives such as int, or objects | Objects only, so Integer instead of int |
| Read a value | a[i] | list.get(i) |
| Change a value | a[i] = x; | list.set(i, x); |
| Add at the end | No room: build a bigger array and copy | list.add(x); |
| Insert or remove in the middle | Shift the other values yourself with a loop | list.add(i, x); and list.remove(i); shift for you |
| How many | a.length | list.size() |
| Print it | Shows something like [I@1b6d3586 | Shows [1, 2, 3] |
| Bad index | ArrayIndexOutOfBoundsException | IndexOutOfBoundsException |
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.
evens, in order. The program then prints the list and how many there were.Try it
Answer each one in your head or on paper first, then open the answer.
list holds after each line, then what the program prints.ArrayList<double> prices = new ArrayList<>(); not compile, and what do you write instead?shopping holds [milk, bread, eggs]. What do shopping.indexOf("eggs"), shopping.indexOf("jam") and shopping.contains("bread") give?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.
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.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].add. Leave the original list unchanged, and print both lists to prove it.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#
- ArrayList in the Java SE 25 API documentation
- Unmodifiable lists, the rules for List.of, in the List documentation
- Extending Collection with List, on dev.java
- Iterating over the elements of a collection, on dev.java
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