4.8.1 Creating an Empty ArrayList
Picture an app that keeps a grocery list. When you open it, the list is empty. During the week you add items, cross some off, and squeeze a forgotten item into the middle. Nobody knows ahead of time how many items there will be.
An array from Topic 4.3 is a poor fit for this job. Its length is set when it is created and can never change. If you make an array of 10 items and need an 11th, you are stuck: you would have to build a bigger array and copy everything over. And removing an item from the middle of an array leaves a gap that you would have to close by hand.
Java has a class built for lists that change: ArrayList. An ArrayList object is a list of elements, kept in order, that can grow and shrink while the program runs. You can add an element, and the list gets one longer. You can remove an element, and the list gets one shorter, with no gap left behind. Because its size can change, we say an ArrayList is mutable in size. (An array’s elements can change, but its length cannot; an ArrayList can change both.)
Importing the class
String, Math, Integer, and Double live in a package named java.lang, which every Java program can use automatically (Topic 1.15). ArrayList lives in a different package, java.util (short for “utilities”), the same package as Scanner. To use a class from any package other than java.lang, you must tell Java where to find it with an import statement at the very top of the file, before the class. You wrote one in Topic 1.4, import java.util.Scanner;, and ArrayList needs the same kind of line:
import java.util.ArrayList;
Read it as “bring in the class ArrayList from the package java.util.” Without this line, Java has never heard of ArrayList.
Declaring and constructing the list
When you create an ArrayList, you also say what type of element it will hold, written in angle brackets after the class name. Here is a list that holds Strings:
ArrayList<String> groceries = new ArrayList<String>();
| Piece | What it means |
|---|---|
ArrayList<String> |
The variable’s type: “an ArrayList whose elements are Strings” |
groceries |
The variable name; like any object variable (Topic 1.13), it holds a reference to the list |
new ArrayList<String>() |
Calls the ArrayList constructor, which takes no arguments and builds an empty list |
The type in the angle brackets is called the type parameter. Java’s documentation writes the class as ArrayList<E>, where E stands for “the type of the elements” and gets replaced by a real type, such as String, when you write your code. A class that takes a type parameter like this is called a generic class. You will see in 4.8.7 why stating the type matters so much.
Notice what the constructor does not take: a size. With an array you wrote new String[10] and got 10 slots. An ArrayList starts with no elements at all and grows only as you add them. (Java also lets you shorten the right side to new ArrayList<>(), leaving the type out of the second pair of brackets because it is already on the left. Both forms work; this guide writes the type both times.)
Key idea
Write import java.util.ArrayList; at the top of the file, then ArrayList<String> groceries = new ArrayList<String>(); to create an empty list of Strings. The constructor ArrayList() always makes a list with no elements.
Worked example: A brand-new list is empty
This program creates a list and asks two questions about it right away. The method size() returns the number of elements in the list as an int; you will use it constantly in the next subsection.
import java.util.ArrayList;
public class EmptyList
{
public static void main(String[] args)
{
ArrayList<String> groceries = new ArrayList<String>();
System.out.println(groceries.size());
System.out.println(groceries);
}
}0
[]Line 1 imports the class. Line 7 creates the list; there are no elements yet, so line 8 prints 0. Line 9 prints the list itself. As you learned in Topic 1.15, printing an object makes Java call that object’s toString method. ArrayList’s version of toString lists the elements inside square brackets, separated by commas, so an empty list prints as []. That bracket format makes it easy to see what a list holds, and this guide uses it in almost every example.
Now vary the element type. The type parameter can be any class: String, Double, Integer, or a class you wrote yourself in Unit 3.
Worked example: Two empty lists of different types
import java.util.ArrayList;
public class EmptyTwoLists
{
public static void main(String[] args)
{
ArrayList<String> names = new ArrayList<String>();
ArrayList<Double> temperatures = new ArrayList<Double>();
System.out.println(names.size());
System.out.println(temperatures.size());
System.out.println(temperatures);
}
}0
0
[]Lines 7 and 8 build two separate lists. names will hold Strings and temperatures will hold Doubles, but right now both are empty, so both sizes are 0. One import line on line 1 covers every ArrayList in the file, whatever its element type.
Watch out
Forgetting the import. This file is the same as EmptyList.java without line 1:
public class MissingImport
{
public static void main(String[] args)
{
ArrayList<String> groceries = new ArrayList<String>();
System.out.println(groceries.size());
}
}MissingImport.java:5: error: cannot find symbol
ArrayList<String> groceries = new ArrayList<String>();
^
symbol: class ArrayList
location: class MissingImportJava reports the same error a second time for the ArrayList after new. The message says Java cannot find a class named ArrayList: since it is not in java.lang, Java does not look for it unless you import it. Students forget the line because most programs since Unit 1 have used only java.lang classes, which need no import. The fix is to add import java.util.ArrayList; as the first line. (Exact wording can vary between Java versions; look for “cannot find symbol” and “class ArrayList”.)
Pause and try
Question 1 · Multiple choice
A student runs ArrayList<Double> prices = new ArrayList<Double>();. Which statement about prices right after this line is true?
- It holds 10 elements, each equal to
0.0. - It holds no elements, so
prices.size()returns0. - It holds one element,
null, as a placeholder. - It cannot be used until a length is written inside the parentheses.
Check your reasoning
B. The ArrayList constructor takes no size and always builds an empty list. Elements appear only when you add them.
- A is wrong: it mixes up an
ArrayListwith an array.new double[10]makes 10 slots filled with0.0(Topic 4.3), but anArrayListstarts with no slots at all. - C is wrong: an empty list has nothing in it, not even
null. Printing it shows[]. - D is wrong:
new ArrayList<Double>()with empty parentheses is the complete constructor call, and the list is ready to use.
Reread 4.8.1 Creating an Empty ArrayList if you chose differently.
Question 2 · Short answer
A program uses String, Math, and ArrayList. Only ArrayList needs an import statement. Explain why.
Check your reasoning
Model answer: String and Math belong to the java.lang package, which every program can use without an import. ArrayList belongs to the java.util package, so the program must say import java.util.ArrayList; at the top of the file. Without it, Java reports cannot find symbol for ArrayList.
A complete answer says:
ArrayListis in thejava.utilpackage.- Classes in
java.lang(likeStringandMath) are available automatically; classes in other packages must be imported.
Reread 4.8.1 Creating an Empty ArrayList if your answer was missing one of these.
4.8.2 Adding to the End and Counting
An empty list is a start, but a grocery list needs items. The simplest way to put an element in a list is to tack it onto the end, the way you write a new item at the bottom of a paper list.
The method add(E obj) does exactly that: it appends obj to the end of the list, and the list’s size goes up by 1. The E in the header is the type parameter from 4.8.1, so for an ArrayList<String>, add takes a String. Its full header is boolean add(E obj): it returns true every time. The return value exists for reasons outside this course, so you will almost always call add as a statement on its own and ignore what it returns.
The method int size() returns how many elements the list holds right now. Because elements come and go, size() can give a different answer each time you call it.
Key idea
list.add(obj) puts obj at the end of the list and makes the list one longer. list.size() returns the current number of elements.
Worked example: Building a grocery list
import java.util.ArrayList;
public class AddToEnd
{
public static void main(String[] args)
{
ArrayList<String> groceries = new ArrayList<String>();
groceries.add("milk");
System.out.println(groceries.size());
groceries.add("eggs");
groceries.add("bread");
System.out.println(groceries.size());
System.out.println(groceries);
}
}| Line | Call | groceries afterward |
size() |
|---|---|---|---|
| 7 | constructor | [] |
0 |
| 8 | groceries.add("milk") |
[milk] |
1 |
| 10 | groceries.add("eggs") |
[milk, eggs] |
2 |
| 11 | groceries.add("bread") |
[milk, eggs, bread] |
3 |
1
3
[milk, eggs, bread]Line 9 asks for the size after one add, so it prints 1. Lines 10 and 11 each add one more, so line 12 prints 3. Line 13 shows the order: elements stay in the order they were added, with the newest at the end.
The next example changes two things: it adds a value that is already in the list, and it prints what add returns.
Worked example: Duplicates are allowed, and add returns true
import java.util.ArrayList;
public class AddDuplicates
{
public static void main(String[] args)
{
ArrayList<String> orders = new ArrayList<String>();
orders.add("taco");
orders.add("salad");
System.out.println(orders.add("taco"));
System.out.println(orders);
System.out.println(orders.size());
}
}true
[taco, salad, taco]
3Line 10 calls add inside println, so the value add returns gets printed: true. The call still does its job, so "taco" is appended a second time. An ArrayList does not check for repeats; "taco" now appears at the front and at the end, and the size counts both, giving 3.
You now know three different ways Java asks “how many?”, and they are easy to mix up:
| Kind of object | How to ask its size | Example |
|---|---|---|
| Array | length (no parentheses; it is not a method) |
scores.length |
String |
length() method |
word.length() |
ArrayList |
size() method |
groceries.size() |
Watch out
Writing length for a list. A student used to arrays and strings writes both of the wrong forms:
import java.util.ArrayList;
public class SizeNotLength
{
public static void main(String[] args)
{
ArrayList<String> groceries = new ArrayList<String>();
groceries.add("milk");
System.out.println(groceries.length);
System.out.println(groceries.length());
}
}SizeNotLength.java:9: error: cannot find symbol
System.out.println(groceries.length);
^
SizeNotLength.java:10: error: cannot find symbol
System.out.println(groceries.length());
^An ArrayList has no length variable and no length() method, so Java cannot find either one. The only way to ask a list for its number of elements is groceries.size(). (Error wording can vary between Java versions; the key words are “cannot find symbol”.)
Pause and try
Question 3 · Tracing
Write the exact output of this program.
import java.util.ArrayList;
public class AddTrace
{
public static void main(String[] args)
{
ArrayList<String> pets = new ArrayList<String>();
System.out.println(pets.size());
pets.add("cat");
pets.add("dog");
System.out.println(pets.size());
pets.add("cat");
pets.add("fish");
System.out.println(pets.size());
System.out.println(pets);
}
}Check your reasoning
0
2
4
[cat, dog, cat, fish]| Line | What happens | pets |
size() |
|---|---|---|---|
| 7 | empty list created | [] |
0 |
| 8 | prints the size | [] |
0 |
| 9–10 | two appends | [cat, dog] |
2 |
| 11 | prints the size | [cat, dog] |
2 |
| 12–13 | two more appends | [cat, dog, cat, fish] |
4 |
| 14 | prints the size | [cat, dog, cat, fish] |
4 |
The second "cat" is a separate element, so it counts toward the size and appears again in the printed list. If you got 3, you assumed a list skips duplicates; it does not.
Reread 4.8.2 Adding to the End and Counting if your output differed.
4.8.3 Indexes and get
A list is only useful if you can look at what is in it. Just like an array (Topic 4.3), each element of an ArrayList has a position number called its index. The first element is at index 0, the next at index 1, and so on. The last element is at index size() - 1, one less than the number of elements, because counting started at 0 instead of 1.
Here is the grocery list from 4.8.2 with its indexes:
index: 0 1 2
value: "milk" "eggs" "bread"
There are 3 elements, so the valid indexes are 0, 1, and 2. There is no index 3.
With an array you would write groceries[1], but square brackets do not work on an ArrayList, because it is an object, not an array. Instead you call the method E get(int index), which returns the element at that index. The return type is E, the list’s element type, so on an ArrayList<String>, get hands back a String. That means you can store the result in a String variable, or call a String method on it right away.
get only looks. It does not remove or change anything, and the size stays the same.
Key idea
The indexes of an ArrayList run from 0 to size() - 1. list.get(i) returns the element at index i, with the list’s element type, and leaves the list unchanged.
Worked example: Reading elements by index
import java.util.ArrayList;
public class GetByIndex
{
public static void main(String[] args)
{
ArrayList<String> groceries = new ArrayList<String>();
groceries.add("milk");
groceries.add("eggs");
groceries.add("bread");
System.out.println(groceries.get(0));
System.out.println(groceries.get(2));
String middle = groceries.get(1);
System.out.println(middle.toUpperCase());
System.out.println(groceries.size());
}
}| Line | Expression | Value | Printed |
|---|---|---|---|
| 11 | groceries.get(0) |
"milk" |
milk |
| 12 | groceries.get(2) |
"bread" |
bread |
| 13 | middle = groceries.get(1) |
"eggs" |
|
| 14 | middle.toUpperCase() |
"EGGS" |
EGGS |
| 15 | groceries.size() |
3 | 3 |
milk
bread
EGGS
3Line 13 stores the returned String in a String variable; this compiles because the list’s element type is String. Line 15 shows that the three get calls did not use up or remove anything: the list still has 3 elements.
Because the last index is always size() - 1, you can reach the last element without knowing how long the list is. The next example uses that idea while the list grows.
Worked example: Always reaching the last element
import java.util.ArrayList;
public class GetLast
{
public static void main(String[] args)
{
ArrayList<String> songs = new ArrayList<String>();
songs.add("Rain");
songs.add("Glow");
System.out.println(songs.get(songs.size() - 1));
songs.add("Tides");
songs.add("Ember");
System.out.println(songs.get(songs.size() - 1));
System.out.println(songs.get(1).length());
}
}| Line | songs |
size() - 1 |
get(size() - 1) |
|---|---|---|---|
| 10 | [Rain, Glow] |
1 | "Glow" |
| 13 | [Rain, Glow, Tides, Ember] |
3 | "Ember" |
Glow
Ember
4The same expression on lines 10 and 13 finds a different element, because size() is recalculated at each call. Line 14 chains two calls: songs.get(1) returns "Glow", and then .length() is called on that String, giving 4.
Watch out
Using size() as an index. Since the list has 3 elements, it is tempting to think index 3 is the last one:
import java.util.ArrayList;
public class GetPastEnd
{
public static void main(String[] args)
{
ArrayList<String> groceries = new ArrayList<String>();
groceries.add("milk");
groceries.add("eggs");
groceries.add("bread");
System.out.println(groceries.get(2));
System.out.println(groceries.get(3));
}
}bread
Exception in thread "main" java.lang.IndexOutOfBoundsException: Index 3 out of bounds for length 3
at GetPastEnd.main(GetPastEnd.java:12)The program compiles, because Java cannot know the list’s size until the program runs. Line 11 works and prints bread. On line 12 the list checks the index, finds that 3 is not between 0 and 2, and throws an IndexOutOfBoundsException, which stops the program. (The real message also lists a few lines from inside the ArrayList class, and the exact wording can vary between Java versions.) This is the list version of the ArrayIndexOutOfBoundsException you met with arrays. The fix is to use groceries.get(2), or groceries.get(groceries.size() - 1) for “the last element.”
Pause and try
Question 4 · Tracing
Write the exact output of this program.
import java.util.ArrayList;
public class GetTrace
{
public static void main(String[] args)
{
ArrayList<String> cities = new ArrayList<String>();
cities.add("Lima");
cities.add("Oslo");
cities.add("Cairo");
cities.add("Pune");
String pick = cities.get(cities.size() - 2);
System.out.println(pick);
System.out.println(cities.get(1).substring(0, 2));
System.out.println(cities.get(0).length() + cities.size());
}
}Check your reasoning
Cairo
Os
8The list is [Lima, Oslo, Cairo, Pune] with size 4.
| Line | Work | Printed |
|---|---|---|
| 12 | cities.size() - 2 is 2, and get(2) is "Cairo" |
|
| 13 | prints pick |
Cairo |
| 14 | get(1) is "Oslo"; substring(0, 2) is "Os" |
Os |
| 15 | get(0).length() is 4 ("Lima"); plus size() 4 gives 8 |
8 |
If you printed Oslo first, you counted from 1 instead of 0. On line 15 both operands are ints, so + adds them.
Reread 4.8.3 Indexes and get if your output differed.
Question 5 · Multiple choice
days is an ArrayList<String> holding [Mon, Tue, Wed, Thu]. Which call throws an IndexOutOfBoundsException?
days.get(0)days.get(3)days.get(days.size() - 1)days.get(4)
Check your reasoning
D. The list has 4 elements, so its valid indexes are 0 through 3. Index 4 is past the end.
- A is wrong: index 0 is the first element,
"Mon". - B is wrong: index 3 is the last element,
"Thu". It is the largest valid index, not an invalid one. - C is wrong:
days.size() - 1is 3, the same as choice B, so it returns"Thu".
Reread 4.8.3 Indexes and get if you chose differently.
4.8.4 Replacing an Element with set
Suppose your chore list says wash, dry, fold, and you decide to iron instead of dry. You do not want a fourth chore; you want to cross out dry and write iron in its place. With an array, you would write chores[1] = "iron";. An ArrayList again uses a method instead of square brackets.
The method E set(int index, E obj) replaces the element at index with obj. The list keeps the same size, and every other element stays where it was. As a bonus, set returns the element that used to be at that index, so you can keep the old value if you need it. (If you do not need it, call set as a statement and ignore the result.)
Because set replaces an element that already exists, the index must be a valid index for get: from 0 to size() - 1.
Key idea
list.set(i, obj) puts obj at index i in place of the element that was there, and returns that old element. The size does not change.
Worked example: Swapping one chore for another
import java.util.ArrayList;
public class SetReplace
{
public static void main(String[] args)
{
ArrayList<String> chores = new ArrayList<String>();
chores.add("wash");
chores.add("dry");
chores.add("fold");
String old = chores.set(1, "iron");
System.out.println(old);
System.out.println(chores);
System.out.println(chores.size());
}
}| Step | Line | What happens | chores |
|---|---|---|---|
| 1 | 8–10 | three appends | [wash, dry, fold] |
| 2 | 11 | set(1, "iron") puts "iron" at index 1 |
[wash, iron, fold] |
| 3 | 11 | set returns the old element, "dry", into old |
[wash, iron, fold] |
dry
[wash, iron, fold]
3Line 12 prints dry, not iron: set returns what was replaced, not what was put in. Line 14 confirms the size is still 3.
Now use that return value for something. Swapping two elements is a classic job: with only set and get, you might worry that the first set destroys a value before you can move it. The returned old element solves that.
Worked example: Swapping the first and last runners
import java.util.ArrayList;
public class SetSwap
{
public static void main(String[] args)
{
ArrayList<String> lineup = new ArrayList<String>();
lineup.add("Ava");
lineup.add("Ben");
lineup.add("Cal");
String moved = lineup.set(0, lineup.get(2));
lineup.set(2, moved);
System.out.println(lineup);
}
}Line 11 does two things. First Java evaluates the argument lineup.get(2), which is "Cal". Then set(0, "Cal") puts "Cal" at index 0 and returns the element it replaced, "Ava", which is stored in moved.
| Line | lineup |
moved |
|---|---|---|
| 8–10 | [Ava, Ben, Cal] |
|
| 11 | [Cal, Ben, Cal] |
"Ava" |
| 12 | [Cal, Ben, Ava] |
"Ava" |
[Cal, Ben, Ava]After line 11, "Cal" appears twice for a moment, and "Ava" is no longer in the list at all. It survives only because set returned it. Line 12 puts it back at index 2.
Watch out
Using set to add a new element. A student wants to put "iron" after fold and writes set with the next index:
import java.util.ArrayList;
public class SetPastEnd
{
public static void main(String[] args)
{
ArrayList<String> chores = new ArrayList<String>();
chores.add("wash");
chores.add("dry");
chores.add("fold");
System.out.println(chores);
chores.set(3, "iron");
System.out.println(chores);
}
}[wash, dry, fold]
Exception in thread "main" java.lang.IndexOutOfBoundsException: Index 3 out of bounds for length 3
at SetPastEnd.main(SetPastEnd.java:12)set can only replace an element that exists. There is nothing at index 3 to replace, so line 12 throws an IndexOutOfBoundsException (wording can vary between Java versions). To put a new element at the end, use chores.add("iron");.
Pause and try
Question 6 · Tracing
Write the exact output of this program.
import java.util.ArrayList;
public class SetTrace
{
public static void main(String[] args)
{
ArrayList<String> seats = new ArrayList<String>();
seats.add("Ira");
seats.add("Jo");
seats.add("Kim");
String first = seats.set(2, "Lee");
String second = seats.set(0, first);
System.out.println(first);
System.out.println(second);
System.out.println(seats);
}
}Check your reasoning
Kim
Ira
[Kim, Jo, Lee]| Line | Call | Returned | seats |
|---|---|---|---|
| 8–10 | three appends | [Ira, Jo, Kim] |
|
| 11 | set(2, "Lee") |
"Kim" into first |
[Ira, Jo, Lee] |
| 12 | set(0, first) |
"Ira" into second |
[Kim, Jo, Lee] |
Line 12 puts the value of first ("Kim") at index 0 and returns the element it replaced, "Ira". If you printed Lee first, you thought set returns the new element; it returns the old one.
Reread 4.8.4 Replacing an Element with set if your output differed.
4.8.5 Inserting in the Middle
Back to the grocery app: you forgot an item and want it near the top, not at the bottom. Or a friend joins a line of people, and you let them in at second place. add(obj) always goes to the end, and set overwrites someone. You need a way to squeeze an element in.
There is a second add method with two parameters: void add(int index, E obj). It inserts obj at position index. To make room, the element that was at index, and every element after it, moves one place to the right: each one’s index goes up by 1. The size goes up by 1. Nothing is lost or overwritten. This version returns nothing (void).
Think of people standing in numbered spots. When someone cuts in at spot 1, the person at spot 1 steps to spot 2, the person at spot 2 steps to spot 3, and so on to the end of the line.
For this method, the valid indexes are 0 through size(), one more than for get and set. Index size() is allowed because inserting there means “after the last element,” which is the end of the list; nothing has to move. Any index larger than size() would leave a gap, so Java refuses it.
Key idea
list.add(i, obj) inserts obj at index i, shifting the element at i and everything after it one place right (each index goes up by 1). The index may be anywhere from 0 to size().
Worked example: Letting Dev into the line
import java.util.ArrayList;
public class InsertMiddle
{
public static void main(String[] args)
{
ArrayList<String> line = new ArrayList<String>();
line.add("Ana");
line.add("Ben");
line.add("Cy");
line.add(1, "Dev");
System.out.println(line);
System.out.println(line.get(2));
System.out.println(line.size());
}
}Before line 11, the list is:
index: 0 1 2
value: "Ana" "Ben" "Cy"Line 11, line.add(1, "Dev"), puts "Dev" at index 1. "Ben" moves from index 1 to 2, and "Cy" moves from 2 to 3:
index: 0 1 2 3
value: "Ana" "Dev" "Ben" "Cy"[Ana, Dev, Ben, Cy]
Ben
4Line 13 shows the shift: get(2) was "Cy" before the insert, but now it is "Ben". After an insert, any index you remembered for a later element is out of date.
Now vary the index: insert at the very front, at the very end, and then one past the end.
Worked example: Inserting at the ends
import java.util.ArrayList;
public class InsertEnds
{
public static void main(String[] args)
{
ArrayList<String> line = new ArrayList<String>();
line.add("Ana");
line.add("Ben");
line.add("Cy");
line.add(0, "Eve");
System.out.println(line);
line.add(line.size(), "Flo");
System.out.println(line);
line.add(line.size() + 1, "Gus");
System.out.println(line);
}
}| Line | Call | Size before | Result |
|---|---|---|---|
| 11 | add(0, "Eve") |
3 | [Eve, Ana, Ben, Cy]; all three shift right |
| 13 | add(line.size(), "Flo") |
4 | inserts at index 4, the end; nothing shifts |
| 15 | add(line.size() + 1, "Gus") |
5 | index 6 is past size(); exception |
[Eve, Ana, Ben, Cy]
[Eve, Ana, Ben, Cy, Flo]
Exception in thread "main" java.lang.IndexOutOfBoundsException: Index: 6, Size: 5
at InsertEnds.main(InsertEnds.java:15)Index 0 is always allowed, and so is index size(): line.add(line.size(), "Flo") does the same thing as line.add("Flo"). Index 6 on a list of size 5 would leave index 5 empty, so line 15 throws an IndexOutOfBoundsException, and line 16 never runs. (Notice this message is worded differently from the one get gives; wording varies by method and Java version, but the exception name is the same.)
The two methods that take an index and an element, set and the two-parameter add, look almost alike, but they do very different things:
Call on [Ana, Ben, Cy] |
Result | Size | Valid indexes |
|---|---|---|---|
set(1, "Dev") |
[Ana, Dev, Cy] |
3 | 0 to size() - 1 |
add(1, "Dev") |
[Ana, Dev, Ben, Cy] |
4 | 0 to size() |
Watch out
Using set when you mean insert, or the reverse. Both lines below take index 1 and "Dev", but only one keeps "Ben":
import java.util.ArrayList;
public class SetVsInsert
{
public static void main(String[] args)
{
ArrayList<String> replaced = new ArrayList<String>();
replaced.add("Ana");
replaced.add("Ben");
replaced.add("Cy");
replaced.set(1, "Dev");
System.out.println(replaced);
ArrayList<String> inserted = new ArrayList<String>();
inserted.add("Ana");
inserted.add("Ben");
inserted.add("Cy");
inserted.add(1, "Dev");
System.out.println(inserted);
}
}[Ana, Dev, Cy]
[Ana, Dev, Ben, Cy]Line 11 replaces "Ben", so "Ben" is gone and the size stays 3. Line 18 inserts, so "Ben" and "Cy" shift right and the size becomes 4. Before you write either one, ask: “Should the element at this index still be in the list afterward?” If yes, use add(index, obj); if it should be replaced, use set(index, obj).
Pause and try
Question 7 · Tracing
Write the exact output of this program. Sketch the list after each line.
import java.util.ArrayList;
public class InsertTrace
{
public static void main(String[] args)
{
ArrayList<String> stops = new ArrayList<String>();
stops.add("Park");
stops.add("Mall");
stops.add(1, "Zoo");
stops.add(0, "Depot");
stops.add(stops.size(), "Bay");
System.out.println(stops);
System.out.println(stops.get(2));
}
}Check your reasoning
[Depot, Park, Zoo, Mall, Bay]
Zoo| Line | Call | stops afterward |
|---|---|---|
| 8–9 | two appends | [Park, Mall] |
| 10 | add(1, "Zoo") |
[Park, Zoo, Mall] |
| 11 | add(0, "Depot") |
[Depot, Park, Zoo, Mall] |
| 12 | add(stops.size(), "Bay") |
[Depot, Park, Zoo, Mall, Bay] |
On line 11, inserting at the front shifts every element, so "Zoo" moves from index 1 to index 2, which is why line 14 prints Zoo. On line 12, stops.size() is 4, and inserting at index 4 appends.
Reread 4.8.5 Inserting in the Middle if your output differed.
4.8.6 Removing an Element
The last thing a grocery list needs is a way to cross items off. When an item is gone, you want the list to close up so there is no hole. With an array that meant shifting elements yourself; an ArrayList does it for you.
The method E remove(int index) removes the element at index. Every element after it moves one place to the left: each one’s index goes down by 1. The size goes down by 1. Like set, it returns the element that was removed, so you can use it after it leaves the list.
Insert and remove are mirror images: add(index, obj) shifts later elements right to open a space, and remove(index) shifts them left to close one. Because remove takes away an element that exists, its valid indexes are 0 to size() - 1, the same as get and set. An index outside that range throws an IndexOutOfBoundsException.
Key idea
list.remove(i) takes out the element at index i, shifts every later element one place left (each index goes down by 1), shrinks the size by 1, and returns the removed element.
Worked example: Dev leaves the line
import java.util.ArrayList;
public class RemoveShift
{
public static void main(String[] args)
{
ArrayList<String> line = new ArrayList<String>();
line.add("Ana");
line.add("Dev");
line.add("Ben");
line.add("Cy");
String gone = line.remove(1);
System.out.println(gone);
System.out.println(line);
System.out.println(line.get(1));
System.out.println(line.size());
}
}Before line 12:
index: 0 1 2 3
value: "Ana" "Dev" "Ben" "Cy"Line 12 removes index 1. "Dev" comes out and is stored in gone. "Ben" moves from index 2 to 1, and "Cy" from 3 to 2:
index: 0 1 2
value: "Ana" "Ben" "Cy"Dev
[Ana, Ben, Cy]
Ben
3Line 15 asks for index 1 again and now gets "Ben", because "Ben" slid into the space "Dev" left.
Next, vary what happens to the removed element. Since remove returns it, you can pass it straight to another method, such as add on a different list.
Worked example: Moving people from waiting to served
import java.util.ArrayList;
public class ServeLine
{
public static void main(String[] args)
{
ArrayList<String> waiting = new ArrayList<String>();
waiting.add("Ana");
waiting.add("Ben");
waiting.add("Cy");
ArrayList<String> served = new ArrayList<String>();
served.add(waiting.remove(0));
served.add(waiting.remove(0));
System.out.println(waiting);
System.out.println(served);
waiting.remove(waiting.size() - 1);
System.out.println(waiting);
System.out.println(waiting.size());
}
}| Line | Call | waiting |
served |
|---|---|---|---|
| 8–10 | three appends to waiting |
[Ana, Ben, Cy] |
|
| 11 | served created |
[Ana, Ben, Cy] |
[] |
| 12 | remove(0) returns "Ana"; add it to served |
[Ben, Cy] |
[Ana] |
| 13 | remove(0) returns "Ben"; add it to served |
[Cy] |
[Ana, Ben] |
| 16 | remove(size() - 1) removes "Cy" |
[] |
[Ana, Ben] |
[Cy]
[Ana, Ben]
[]
0Lines 12 and 13 both say remove(0), yet they remove different people: after "Ana" leaves, "Ben" shifts to index 0. Line 16 removes the last element; nothing comes after it, so nothing shifts. Its return value is ignored, which is fine. The list is now empty, size 0.
Watch out
Removing two elements with indexes from before the first removal. A coach wants to drop Ben (index 1) and Dev (index 3) from [Ana, Ben, Cy, Dev, Eve], and writes:
import java.util.ArrayList;
public class RemoveTwoWrong
{
public static void main(String[] args)
{
ArrayList<String> team = new ArrayList<String>();
team.add("Ana");
team.add("Ben");
team.add("Cy");
team.add("Dev");
team.add("Eve");
team.remove(1);
team.remove(3);
System.out.println(team);
}
}[Ana, Cy, Dev]Dev is still on the team, and Eve was dropped instead. The coach looked up both indexes in the original list [Ana, Ben, Cy, Dev, Eve]. But line 13 removed Ben, and every later element shifted left: Cy to 1, Dev to 2, Eve to 3. So line 14’s remove(3) removed Eve.
One fix is to remove the higher index first. Removing index 3 shifts only the elements after it, so index 1 still means Ben:
import java.util.ArrayList;
public class RemoveTwoFixed
{
public static void main(String[] args)
{
ArrayList<String> team = new ArrayList<String>();
team.add("Ana");
team.add("Ben");
team.add("Cy");
team.add("Dev");
team.add("Eve");
team.remove(3);
team.remove(1);
System.out.println(team);
}
}[Ana, Cy, Eve]The general rule: after any add(index, obj) or remove(index), the indexes of later elements are different. Work out each index from the list as it is at that moment.
Pause and try
Question 8 · Tracing
Write the exact output of this program.
import java.util.ArrayList;
public class RemoveTrace
{
public static void main(String[] args)
{
ArrayList<String> books = new ArrayList<String>();
books.add("Holes");
books.add("Wonder");
books.add("Dune");
books.add("Matilda");
String a = books.remove(0);
String b = books.remove(books.size() - 1);
books.add(1, a);
System.out.println(b);
System.out.println(books);
System.out.println(books.get(1));
}
}Check your reasoning
Matilda
[Wonder, Holes, Dune]
Holes| Line | Call | Returned | books afterward |
|---|---|---|---|
| 8–11 | four appends | [Holes, Wonder, Dune, Matilda] |
|
| 12 | remove(0) |
"Holes" into a |
[Wonder, Dune, Matilda] |
| 13 | remove(books.size() - 1), which is remove(2) |
"Matilda" into b |
[Wonder, Dune] |
| 14 | add(1, a) |
[Wonder, Holes, Dune] |
On line 13, books.size() is 3 (not 4), because line 12 already shrank the list. If you thought line 13 removed index 3, you used the size from before the first removal.
Reread 4.8.6 Removing an Element if your output differed.
Question 9 · Debugging
The list starts as [email, lunch, report, nap]. The programmer wants to remove lunch and report, leaving [email, nap], but the program prints [email, report]. Explain the bug and give a corrected version of lines 12 and 13.
import java.util.ArrayList;
public class CleanupBug
{
public static void main(String[] args)
{
ArrayList<String> tasks = new ArrayList<String>();
tasks.add("email");
tasks.add("lunch");
tasks.add("report");
tasks.add("nap");
tasks.remove(1);
tasks.remove(2);
System.out.println(tasks);
}
}Check your reasoning
Line 12 removes lunch at index 1, and the later elements shift left: report moves to index 1 and nap to index 2. Line 13’s remove(2) then removes nap, not report. The programmer used index 2 from the original list, but it was out of date after the first removal.
Two corrections work:
tasks.remove(2);
tasks.remove(1);Removing the higher index first does not disturb index 1. Or, since report slides into index 1 after the first removal:
tasks.remove(1);
tasks.remove(1);Either version prints [email, nap]. Java gave no error for the original, because index 2 was still valid; it just held a different element.
Reread 4.8.6 Removing an Element if you missed it.
4.8.7 The Element Type and Wrapper Classes
You have used the type parameter since 4.8.1, writing <String> without much thought. Now look at why it is there, and what happens when you want a list of numbers.
The compiler checks the element type
When you write ArrayList<String>, every method that takes or returns an element uses String in place of E. So for that list, add expects a String, set expects a String and returns a String, and get and remove return Strings. The compiler knows this, so it can catch a wrong type before the program ever runs.
Worked example: Two type mistakes the compiler catches
import java.util.ArrayList;
public class WrongType
{
public static void main(String[] args)
{
ArrayList<String> names = new ArrayList<String>();
names.add("Rosa");
names.add(42);
int letters = names.get(0);
System.out.println(letters);
}
}WrongType.java:9: error: incompatible types: int cannot be converted to String
names.add(42);
^
WrongType.java:10: error: incompatible types: String cannot be converted to int
int letters = names.get(0);
^Line 9 tries to put an int into a list of Strings. Line 10 tries to store what get returns, a String, in an int variable. Both are caught at compile time because the compiler knows the element type is String. (Error wording can vary between Java versions.)
Java does let you write ArrayList with no type parameter at all, as in ArrayList list = new ArrayList();. Such a list accepts any kind of object, so the compiler cannot check what goes in or what comes out. A mistake like line 9 would compile, and the problem would only surface later, as an error while the program runs, which is harder to track down. That is why ArrayList<E> with a real type is always preferred: it lets the compiler find errors that would otherwise show up only at run time. Always write the type parameter.
Lists of numbers need wrapper classes
An ArrayList holds object references, not primitive values. (You will see what that means for objects in 4.8.8.) The type parameter must therefore be a class, never a primitive type such as int or double:
import java.util.ArrayList;
public class IntList
{
public static void main(String[] args)
{
ArrayList<int> scores = new ArrayList<int>();
scores.add(88);
System.out.println(scores);
}
}IntList.java:7: error: unexpected type
ArrayList<int> scores = new ArrayList<int>();
^
required: reference
found: int
The message says the compiler needed a reference type and found the primitive int (and it reports the same error for the second <int>). This is exactly the job the wrapper classes from Topic 4.7 were made for. Use Integer for whole numbers and Double for decimals:
ArrayList<Integer> scores = new ArrayList<Integer>();
Then autoboxing and unboxing from Topic 4.7 do the conversions for you. When you pass an int to add, which expects an Integer, Java boxes it into an Integer automatically. When you assign the Integer that get returns to an int variable, Java unboxes it.
Key idea
The element type of an ArrayList must be a class. For numbers, use ArrayList<Integer> or ArrayList<Double>; autoboxing turns an int or double into an object when you add it, and unboxing turns it back when you store the result of get, set, or remove in a primitive variable.
Worked example: A list of test scores
import java.util.ArrayList;
public class ScoresList
{
public static void main(String[] args)
{
ArrayList<Integer> scores = new ArrayList<Integer>();
scores.add(88);
scores.add(92);
scores.add(75);
int first = scores.get(0);
int total = scores.get(1) + scores.get(2);
System.out.println(first);
System.out.println(total);
int oldScore = scores.set(2, 80);
System.out.println(oldScore);
System.out.println(scores);
}
}| Line | What Java does | scores |
|---|---|---|
| 8–10 | Each int (88, 92, 75) is autoboxed into an Integer and appended |
[88, 92, 75] |
| 11 | get(0) returns the Integer 88; assigning it to an int unboxes it |
[88, 92, 75] |
| 12 | get(1) and get(2) are unboxed for +, giving 92 + 75 = 167 |
[88, 92, 75] |
| 15 | 80 is autoboxed; set returns the old Integer 75, unboxed into oldScore |
[88, 92, 80] |
88
167
75
[88, 92, 80]Double works the same way. This example averages two temperatures and inserts a third at the front.
Worked example: A list of temperatures
import java.util.ArrayList;
public class TemperatureList
{
public static void main(String[] args)
{
ArrayList<Double> temps = new ArrayList<Double>();
temps.add(71.5);
temps.add(68.0);
double average = (temps.get(0) + temps.get(1)) / temps.size();
System.out.println(average);
temps.add(0, 75.25);
System.out.println(temps);
}
}Line 10 unboxes 71.5 and 68.0, adds them to get 139.5, and divides by temps.size(), which is 2. A double divided by an int is a double (Topic 1.3), so average is 69.75. Line 12 boxes 75.25 and inserts it at index 0, shifting the other two right.
69.75
[75.25, 71.5, 68.0]Notice 68.0 prints with its .0: it is a Double, so it prints like a double.
Watch out
Expecting remove to remove a value. In a list of Integers, the number you pass to remove still means an index, not “the element equal to this number”:
import java.util.ArrayList;
public class RemoveIndexNotValue
{
public static void main(String[] args)
{
ArrayList<Integer> nums = new ArrayList<Integer>();
nums.add(40);
nums.add(2);
nums.add(7);
nums.add(1);
int removed = nums.remove(1);
System.out.println(removed);
System.out.println(nums);
}
}2
[40, 7, 1]The programmer wanted to remove the value 1, but nums.remove(1) removed whatever was at index 1, which was 2. The 1 at the end of the list is untouched. Students make this mistake because the list holds numbers and the argument is a number, so it reads like “remove 1.” The remove method in this course always takes an index. To remove the 1 here, use its index: nums.remove(3).
Pause and try
Question 10 · Debugging
This program does not compile. Explain the error and write a corrected line 9.
import java.util.ArrayList;
public class PricesBug
{
public static void main(String[] args)
{
ArrayList<Double> prices = new ArrayList<Double>();
prices.add(2.5);
prices.add(3);
System.out.println(prices);
}
}PricesBug.java:9: error: incompatible types: int cannot be converted to Double
prices.add(3);
^Check your reasoning
The list’s element type is Double, so add expects a Double. The literal 3 is an int. Autoboxing only wraps a primitive in its own wrapper class: an int becomes an Integer, and only a double becomes a Double. Java will not turn an int into a Double, so it reports incompatible types. (Line 8 compiles because 2.5 is a double.)
Corrected line 9:
prices.add(3.0);3.0 is a double, so it is autoboxed into a Double, and the program prints [2.5, 3.0].
Reread 4.8.7 The Element Type and Wrapper Classes if you missed it.
Question 11 · Tracing
Write the exact output of this program.
import java.util.ArrayList;
public class NumbersTrace
{
public static void main(String[] args)
{
ArrayList<Integer> laps = new ArrayList<Integer>();
laps.add(5);
laps.add(3);
laps.add(0, 9);
int gone = laps.remove(2);
int changed = laps.set(0, gone + laps.get(1));
System.out.println(gone);
System.out.println(changed);
System.out.println(laps);
}
}Check your reasoning
3
9
[8, 5]| Line | Call | Returned | laps afterward |
|---|---|---|---|
| 8–9 | two appends | [5, 3] |
|
| 10 | add(0, 9) |
[9, 5, 3] |
|
| 11 | remove(2) |
3, into gone |
[9, 5] |
| 12 | set(0, gone + laps.get(1)), which is set(0, 3 + 5) |
9, into changed |
[8, 5] |
On line 12, Java evaluates the argument first: laps.get(1) is 5 (unboxed), so the new value is 8. set then returns the old value at index 0, which is 9. If you wrote 8 for changed, you thought set returns the new element.
Reread 4.8.7 The Element Type and Wrapper Classes and 4.8.4 Replacing an Element with set if your output differed.
4.8.8 A List Holds References to Objects
In Topic 3.6 you learned that a variable of a class type holds a reference to an object, like an address, and that copying the variable copies the address, not the object. An ArrayList follows the same rule. Each element of a list is a reference. When you add an object, the list stores a copy of the reference, so the list and your variable now point to the same object. No copy of the object is made.
This matters as soon as the objects can change. Here is a Player class from Unit 3 with a method that changes the player’s points. (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.)
Key idea
An ArrayList stores references to objects. Adding an object to a list does not copy it, so a change made through the list (team.get(0).addPoints(3)) and a change made through another variable that refers to the same object are changes to one object.
Worked example: One player, two ways to reach it
import java.util.ArrayList;
class Player
{
private String name;
private int points;
public Player(String name, int points)
{
this.name = name;
this.points = points;
}
public String getName()
{
return name;
}
public int getPoints()
{
return points;
}
public void addPoints(int amount)
{
points = points + amount;
}
}
public class TeamRefs
{
public static void main(String[] args)
{
ArrayList<Player> team = new ArrayList<Player>();
Player kai = new Player("Kai", 10);
team.add(kai);
team.add(new Player("Mo", 4));
kai.addPoints(5);
System.out.println(team.get(0).getPoints());
team.get(0).addPoints(3);
System.out.println(kai.getPoints());
System.out.println(team.get(1).getName());
}
}After lines 34 through 37, memory looks like this:
kai ─────────────────────► Player "Kai", 10
team ──► index 0: ref ────► (the same Player as kai)
index 1: ref ────► Player "Mo", 4| Line | What happens | Kai’s points |
|---|---|---|
| 36 | team.add(kai) stores a copy of the reference in kai |
10 |
| 37 | A new Player is created and its reference is added; no variable names it |
10 |
| 38 | kai.addPoints(5) changes the shared object |
15 |
| 39 | team.get(0) returns the same reference, so getPoints() gives 15 |
15 |
| 40 | team.get(0).addPoints(3) changes the shared object through the list |
18 |
| 41 | kai.getPoints() sees that change |
18 |
15
18
MoLine 40 chains two calls: team.get(0) returns a reference to Kai’s Player, and .addPoints(3) is called on that object. Line 42 reaches the second player, which has no variable of its own; the list is the only way to get to it.
Now vary two things: add the same reference twice, and use set to replace a player. set replaces the reference stored in the list; it does not destroy the old object, and it returns a reference to it.
Worked example: The same player twice, then a replacement
import java.util.ArrayList;
class Player
{
private String name;
private int points;
public Player(String name, int points)
{
this.name = name;
this.points = points;
}
public String getName()
{
return name;
}
public int getPoints()
{
return points;
}
public void addPoints(int amount)
{
points = points + amount;
}
}
public class SetOldPlayer
{
public static void main(String[] args)
{
ArrayList<Player> team = new ArrayList<Player>();
Player star = new Player("Kai", 10);
team.add(star);
team.add(star);
team.get(1).addPoints(4);
System.out.println(team.get(0).getPoints());
Player benched = team.set(0, new Player("Lu", 0));
System.out.println(benched.getName());
System.out.println(benched.getPoints());
System.out.println(team.get(0).getName());
System.out.println(team.get(1).getName());
}
}| Line | What happens | Index 0 | Index 1 |
|---|---|---|---|
| 36–37 | The reference in star is added twice; both elements point to one Player |
Kai, 10 | Kai, 10 |
| 38 | team.get(1).addPoints(4) changes that one Player |
Kai, 14 | Kai, 14 |
| 40 | set(0, new Player("Lu", 0)) puts Lu’s reference at index 0 and returns Kai’s |
Lu, 0 | Kai, 14 |
14
Kai
14
Lu
KaiLine 39 prints 14 even though the points were added through index 1: both indexes held the same reference. On line 40, benched receives the reference that used to be at index 0, so lines 41 and 42 can still use Kai’s object. Kai is still in the list at index 1, because only index 0 was replaced.
Watch out
Thinking that reassigning a variable changes the list. After adding a String, a student assigns a new value to the variable and expects the list to follow:
import java.util.ArrayList;
public class StringReassign
{
public static void main(String[] args)
{
ArrayList<String> groceries = new ArrayList<String>();
String item = "milk";
groceries.add(item);
item = "oat milk";
System.out.println(item);
System.out.println(groceries);
}
}oat milk
[milk]Line 9 stores a copy of the reference in item, which points to "milk". Line 10 makes item point to a different object, "oat milk". That changes what the variable holds, not what the list holds; the list still points to "milk". This is different from kai.addPoints(5) in TeamRefs.java, which changed the object itself. (A String object can never be changed at all, as you learned in Unit 1.) To change what the list holds, call a list method: groceries.set(0, "oat milk"); makes the list print [oat milk].
Pause and try
Question 12 · Tracing
Using the same Player class, write the exact output of this program.
import java.util.ArrayList;
class Player
{
private String name;
private int points;
public Player(String name, int points)
{
this.name = name;
this.points = points;
}
public String getName()
{
return name;
}
public int getPoints()
{
return points;
}
public void addPoints(int amount)
{
points = points + amount;
}
}
public class RefsTrace
{
public static void main(String[] args)
{
ArrayList<Player> squad = new ArrayList<Player>();
Player a = new Player("Rin", 2);
Player b = new Player("Sol", 6);
squad.add(a);
squad.add(b);
squad.add(a);
a.addPoints(1);
squad.get(1).addPoints(10);
Player out = squad.remove(0);
out.addPoints(5);
System.out.println(squad.get(0).getPoints());
System.out.println(squad.get(1).getPoints());
System.out.println(squad.size());
}
}Check your reasoning
16
8
2Two Player objects exist: Rin (a) and Sol (b). The list holds three references: [Rin, Sol, Rin].
| Line | What happens | Rin | Sol | squad |
|---|---|---|---|---|
| 39 | after three adds | 2 | 6 | [Rin, Sol, Rin] |
| 40 | a.addPoints(1) |
3 | 6 | [Rin, Sol, Rin] |
| 41 | squad.get(1) is Sol; add 10 |
3 | 16 | [Rin, Sol, Rin] |
| 42 | remove(0) removes one reference to Rin and returns it into out |
3 | 16 | [Sol, Rin] |
| 43 | out.addPoints(5) changes Rin’s object |
8 | 16 | [Sol, Rin] |
Line 44 prints Sol’s points (16), line 45 prints index 1, which is still a reference to Rin (8), and line 46 prints the size, 2. Removing a reference from the list does not destroy the object, and the other reference to Rin still sees the change.
Reread 4.8.8 A List Holds References to Objects if your output differed.
Putting it together
A playlist is a natural ArrayList: songs are added, one can be bumped up to play next, and skipping a song sends it to the back. This Playlist class keeps its songs in a private ArrayList<String> instance variable and uses every method from the chapter. Before reading the trace, predict all five lines of output.
Worked example: A road-trip playlist
import java.util.ArrayList;
class Playlist
{
private ArrayList<String> songs;
public Playlist()
{
songs = new ArrayList<String>();
}
public void addSong(String title)
{
songs.add(title);
}
public void playNext(String title)
{
songs.add(1, title);
}
public String skip()
{
String skipped = songs.remove(0);
songs.add(skipped);
return skipped;
}
public String nowPlaying()
{
return songs.get(0);
}
public int getCount()
{
return songs.size();
}
public void show()
{
System.out.println(songs);
}
}
public class PlaylistDemo
{
public static void main(String[] args)
{
Playlist trip = new Playlist();
trip.addSong("Rain");
trip.addSong("Glow");
trip.addSong("Tides");
trip.playNext("Ember");
trip.show();
System.out.println(trip.skip());
System.out.println(trip.nowPlaying());
trip.show();
System.out.println(trip.getCount());
}
}Here is what each method does, in terms of the list:
| Method | Line | List call | Effect |
|---|---|---|---|
| constructor | 9 | new ArrayList<String>() |
starts with an empty list |
addSong |
14 | songs.add(title) |
appends to the end |
playNext |
19 | songs.add(1, title) |
inserts right after the current song |
skip |
24–26 | songs.remove(0), then songs.add(skipped) |
moves the first song to the back and returns it |
nowPlaying |
31 | songs.get(0) |
returns the first song |
getCount |
36 | songs.size() |
returns the number of songs |
show |
41 | System.out.println(songs) |
prints the list in brackets |
Now trace main:
| Line | Call | songs afterward |
Printed |
|---|---|---|---|
| 49 | new Playlist() |
[] |
|
| 50–52 | three addSong calls |
[Rain, Glow, Tides] |
|
| 53 | playNext("Ember") |
[Rain, Ember, Glow, Tides] |
|
| 54 | show() |
[Rain, Ember, Glow, Tides] |
[Rain, Ember, Glow, Tides] |
| 55 | skip(): remove index 0 ("Rain"), append it, return it |
[Ember, Glow, Tides, Rain] |
Rain |
| 56 | nowPlaying() |
[Ember, Glow, Tides, Rain] |
Ember |
| 57 | show() |
[Ember, Glow, Tides, Rain] |
[Ember, Glow, Tides, Rain] |
| 58 | getCount() |
[Ember, Glow, Tides, Rain] |
4 |
[Rain, Ember, Glow, Tides]
Rain
Ember
[Ember, Glow, Tides, Rain]
4Three things to notice:
- On line 53, inserting at index 1 shifts
"Glow"and"Tides"right; nothing is overwritten. IfplayNexthad usedset(1, title),"Glow"would be lost. - In
skip, the returned value ofremove(0)is used twice: it is appended to the end and also returned tomain. Removing shifts everyone left, so"Ember"becomes the new song at index 0. mainnever touches theArrayListdirectly. The list is a private instance variable, and the methods decide which list calls are allowed, just like the classes you designed in Unit 3.
Common mistakes
The ArrayList methods look alike, and the most common errors come from mixing them up or from using an index that is out of range. Use this table as a quick lookup:
| Call | Valid indexes | Size afterward | Returns |
|---|---|---|---|
size() |
(none) | same | number of elements |
add(obj) |
(none) | +1 | true |
add(i, obj) |
0 to size() |
+1 | nothing (void) |
get(i) |
0 to size() - 1 |
same | the element at i |
set(i, obj) |
0 to size() - 1 |
same | the old element |
remove(i) |
0 to size() - 1 |
−1 | the removed element |
An index outside the valid range throws an IndexOutOfBoundsException when the program runs.
Vocabulary
| Term | Definition | Example |
|---|---|---|
ArrayList |
A class whose objects are ordered lists that can grow and shrink while a program runs | ArrayList<String> groceries |
| mutable in size | Able to change how many elements it holds after it is created | add and remove change size() |
java.util |
The package that contains ArrayList |
import java.util.ArrayList; |
| import statement | A line at the top of a file that makes a class from another package available | import java.util.ArrayList; |
ArrayList constructor |
ArrayList(), which builds an empty list |
new ArrayList<String>() |
| empty list | A list with no elements; its size is 0 and it prints as [] |
a newly constructed list |
| type parameter | The type in angle brackets that says what kind of element a list holds; written E in the documentation |
String in ArrayList<String> |
| generic | Describes a class that takes a type parameter | ArrayList<E> |
| append | To add an element at the end of a list | groceries.add("milk") |
| index | An element’s position number, from 0 to size() - 1 |
"eggs" is at index 1 |
| insert | To add an element at a given index, shifting later elements right | line.add(1, "Dev") |
IndexOutOfBoundsException |
The runtime error thrown when a list method gets an index outside its valid range | get(3) on a list of size 3 |
Chapter summary
- An
ArrayListis an ordered list that can grow and shrink. It is in thejava.utilpackage, so a file that uses it needsimport java.util.ArrayList;. The constructornew ArrayList<String>()makes an empty list. ArrayList<E>names the element type. The methods use that type for their parameters and return values, so the compiler can catch a wrong type before the program runs. Always write the type; numbers go inArrayList<Integer>orArrayList<Double>, with autoboxing and unboxing doing the conversions.size()counts the elements,add(obj)appends,get(i)returns an element, andset(i, obj)replaces one and returns the old element.add(i, obj)inserts and shifts later elements right;remove(i)removes, shifts later elements left, and returns the removed element. Either one changes the indexes of every later element.- Indexes run from 0 to
size() - 1(up tosize()for inserting). Any other index throws anIndexOutOfBoundsException. - A list holds references to objects, so changing an object through the list or through another variable changes the same object.
Review questions
Question 13 · Multiple choice
names is an ArrayList<String> holding [Ali, Bo, Cam]. Which call throws an IndexOutOfBoundsException?
names.add(3, "Dee")names.set(3, "Dee")names.get(2)names.add("Dee")
Check your reasoning
B. set replaces an existing element, so its index must be 0 to size() - 1, which is 0 to 2 here. There is no element at index 3 to replace.
- A is wrong: inserting allows indexes 0 to
size(), andsize()is 3, soadd(3, "Dee")appends"Dee"at the end. - C is wrong: index 2 is the last element,
"Cam". - D is wrong:
add(obj)takes no index and always appends.
Reread 4.8.4 Replacing an Element with set and 4.8.5 Inserting in the Middle if you chose differently.
Question 14 · Tracing
This program uses every list method. Write the exact output.
import java.util.ArrayList;
public class MixedOps
{
public static void main(String[] args)
{
ArrayList<String> colors = new ArrayList<String>();
colors.add("red");
colors.add("blue");
colors.add("green");
colors.add(1, "pink");
String x = colors.set(2, "gold");
String y = colors.remove(0);
colors.add(y.toUpperCase());
System.out.println(x + " " + y);
System.out.println(colors);
System.out.println(colors.get(colors.size() - 2));
}
}Check your reasoning
blue red
[pink, gold, green, RED]
green| Line | Call | Returned | colors afterward |
|---|---|---|---|
| 8–10 | three appends | [red, blue, green] |
|
| 11 | add(1, "pink") |
[red, pink, blue, green] |
|
| 12 | set(2, "gold") |
"blue" into x |
[red, pink, gold, green] |
| 13 | remove(0) |
"red" into y |
[pink, gold, green] |
| 14 | add(y.toUpperCase()) |
[pink, gold, green, RED] |
Line 15 concatenates x, a space, and y. Line 17: size() is 4, so get(2) is "green". The key step is line 12: after the insert on line 11, index 2 holds "blue", not "green".
Reread 4.8.5 Inserting in the Middle and 4.8.6 Removing an Element if your output differed.
Question 15 · Debugging
The programmer wants to print the name of the person who signed up most recently. The program prints 3 and then crashes. Explain the bug and write a corrected line 12.
import java.util.ArrayList;
public class LastNameBug
{
public static void main(String[] args)
{
ArrayList<String> signups = new ArrayList<String>();
signups.add("Ada");
signups.add("Grace");
signups.add("Alan");
System.out.println(signups.size());
String newest = signups.get(signups.size());
System.out.println(newest);
}
}3
Exception in thread "main" java.lang.IndexOutOfBoundsException: Index 3 out of bounds for length 3
at LastNameBug.main(LastNameBug.java:12)Check your reasoning
The most recent sign-up is the last element, because add appends. With 3 elements, the indexes are 0, 1, and 2, so the last index is size() - 1, which is 2. signups.size() is 3, which is one past the end, so get throws an IndexOutOfBoundsException.
Corrected line 12:
String newest = signups.get(signups.size() - 1);The program then prints 3 and Alan.
Reread 4.8.3 Indexes and get if you missed it.
Question 16 · Multiple choice
What does this program print?
import java.util.ArrayList;
public class RemoveThenGet
{
public static void main(String[] args)
{
ArrayList<Integer> vals = new ArrayList<Integer>();
vals.add(10);
vals.add(20);
vals.add(30);
int result = vals.remove(1) + vals.get(1);
System.out.println(result);
System.out.println(vals);
}
}50then[10, 30]40then[10, 30]50then[10, 20, 30]- Nothing; the result of
removecannot be added to the result ofget
Check your reasoning
A. Java evaluates vals.remove(1) first. It removes the element at index 1, 20, returns it, and shifts 30 left to index 1. Then vals.get(1) returns 30. Both Integers are unboxed for +, so result is 20 + 30 = 50, and the list is [10, 30].
- B is wrong:
40comes from thinkingget(1)still finds20, as if nothing shifted. After the removal, index 1 holds30. - C is wrong:
removereally takes the element out of the list; it does not just look at it. - D is wrong: both calls return
Integers, which are unboxed intoints for+, so the line compiles.
Reread 4.8.6 Removing an Element and 4.8.7 The Element Type and Wrapper Classes if you chose differently.
Question 17 · Tracing
In Topic 3.6 you learned that passing an object to a method passes a copy of its reference. Use that idea to trace this program.
import java.util.ArrayList;
public class ClubRoster
{
public static void addHonor(ArrayList<String> roster, String name)
{
roster.add(0, name);
}
public static void main(String[] args)
{
ArrayList<String> club = new ArrayList<String>();
club.add("Ivy");
club.add("Max");
addHonor(club, "Zoe");
ArrayList<String> same = club;
same.remove(club.size() - 1);
System.out.println(club);
System.out.println(same.size());
}
}Check your reasoning
[Zoe, Ivy]
2| Line | What happens | The one list |
|---|---|---|
| 12–14 | club is created and two names are appended |
[Ivy, Max] |
| 15 | addHonor(club, "Zoe"): roster gets a copy of the reference, so it is the same list |
[Ivy, Max] |
| 7 | roster.add(0, name) inserts at the front of that list |
[Zoe, Ivy, Max] |
| 16 | same = club copies the reference; still one list |
[Zoe, Ivy, Max] |
| 17 | club.size() - 1 is 2; same.remove(2) removes "Max" |
[Zoe, Ivy] |
There is only one ArrayList object in this program. club, roster, and same are three variables that refer to it, so a change through any of them is seen through all of them.
Reread 4.8.8 A List Holds References to Objects and 4.8.5 Inserting in the Middle if your trace differed.
Question 18 · Short answer
A student asks two questions: “Why can’t I write ArrayList<int>?” and “Why bother writing <Integer> at all, if Java lets me write plain ArrayList?” Answer both.
Check your reasoning
Model answer: An ArrayList holds object references, so its type parameter must be a class. int is a primitive type, so ArrayList<int> does not compile; you write ArrayList<Integer> instead, and autoboxing and unboxing convert between int and Integer automatically. Writing the type parameter tells the compiler what every element must be, so it can reject a wrong type (such as adding a String to a list of Integers) at compile time. A plain ArrayList accepts anything, so the same mistake would not be caught until the program runs.
A complete answer says:
- The element type must be a class (a reference type), so use the wrapper class
Integer. - With
ArrayList<Integer>, the compiler checks the types going in and coming out. - Without a type parameter, type mistakes show up only at run time, which makes them harder to find.
Reread 4.8.7 The Element Type and Wrapper Classes if your answer was missing one of these.
Question 19 · Tracing
This program uses the Playlist class from Putting it together with a different main. Write the exact output.
import java.util.ArrayList;
class Playlist
{
private ArrayList<String> songs;
public Playlist()
{
songs = new ArrayList<String>();
}
public void addSong(String title)
{
songs.add(title);
}
public void playNext(String title)
{
songs.add(1, title);
}
public String skip()
{
String skipped = songs.remove(0);
songs.add(skipped);
return skipped;
}
public String nowPlaying()
{
return songs.get(0);
}
public int getCount()
{
return songs.size();
}
public void show()
{
System.out.println(songs);
}
}
public class PlaylistReview
{
public static void main(String[] args)
{
Playlist mix = new Playlist();
mix.addSong("Echo");
mix.addSong("Bloom");
mix.playNext("Drift");
System.out.println(mix.skip());
mix.playNext("Spark");
mix.show();
System.out.println(mix.nowPlaying());
System.out.println(mix.getCount());
}
}Check your reasoning
Echo
[Drift, Spark, Bloom, Echo]
Drift
4| Line | Call | songs afterward |
Printed |
|---|---|---|---|
| 50–51 | two addSong calls |
[Echo, Bloom] |
|
| 52 | playNext("Drift"): insert at index 1 |
[Echo, Drift, Bloom] |
|
| 53 | skip(): remove "Echo" from index 0, append it, return it |
[Drift, Bloom, Echo] |
Echo |
| 54 | playNext("Spark"): insert at index 1 |
[Drift, Spark, Bloom, Echo] |
|
| 55 | show() |
[Drift, Spark, Bloom, Echo] |
the list |
| 56 | nowPlaying() is get(0) |
[Drift, Spark, Bloom, Echo] |
Drift |
| 57 | getCount() is size() |
[Drift, Spark, Bloom, Echo] |
4 |
The step that trips people up is line 54: after the skip, the song at index 0 is "Drift", so "Spark" goes right after it, pushing "Bloom" and "Echo" right.
Reread Putting it together, 4.8.5 Inserting in the Middle, and 4.8.6 Removing an Element if your output differed.
Reflection
- I can import ArrayList and create an empty list of a chosen element type. Reread
- I can use add and size to build a list and say how many elements it has. Reread
- I can find an element with get, name the valid indexes, and explain an IndexOutOfBoundsException. Reread
- I can replace an element with set and say what set returns. Reread
- I can insert an element at an index and show how later elements shift. Reread
- I can remove an element, use what remove returns, and keep track of shifted indexes. Reread
- I can store numbers in an ArrayList with Integer or Double and explain why the element type matters. Reread
- I can trace code where a list and a variable refer to the same object. Reread