ArrayList
A list that grows, at the cost of boxing — and one removal bug that appears on the exam almost every year.
By the end of this chapter you can
- Use the ArrayList methods the exam tests
- Explain why an ArrayList holds objects rather than primitives
- Remove elements while iterating without skipping any
- Choose between an array and an ArrayList
An array’s length is fixed at creation. ArrayList fixes that, and the price is
that it stores objects, not primitives — which is where its two surprises
come from.
The methods that matter
import java.util.ArrayList;
public class Main {
public static void main(String[] args) {
ArrayList<String> names = new ArrayList<>();
names.add("Ana"); // append
names.add("Ben");
names.add("Cy");
names.add(1, "Bo"); // insert at index, shifting the rest
System.out.println(names);
System.out.println("size " + names.size());
System.out.println("get(0) " + names.get(0));
names.set(0, "Anna"); // replace
String gone = names.remove(2); // remove by index, returns what left
System.out.println("removed " + gone);
System.out.println(names);
System.out.println("contains Cy: " + names.contains("Cy"));
System.out.println("indexOf Cy : " + names.indexOf("Cy"));
}
}Note size() rather than length, and get(i) rather than [i]. Different
spellings for the same ideas, and the exam expects both fluently.
Why ArrayList<int> does not compile
import java.util.ArrayList;
public class Main {
public static void main(String[] args) {
ArrayList<int> numbers = new ArrayList<>(); // will not compile
numbers.add(5);
}
}That sample is asserted not to compile, and the verifier checks that it really fails — an “expect-error” example that quietly started working would be teaching the opposite of what it claims.
The fix is the wrapper type:
import java.util.ArrayList;
public class Main {
public static void main(String[] args) {
ArrayList<Integer> numbers = new ArrayList<>();
numbers.add(5); // autoboxed: int 5 becomes Integer 5
numbers.add(10);
int first = numbers.get(0); // auto-unboxed back to int
System.out.println("sum " + (first + numbers.get(1)));
// Each element is a separate object with a header, which is why an
// ArrayList<Integer> uses several times the memory of an int[].
System.out.println(numbers);
}
}Autoboxing makes this mostly invisible, which is convenient and occasionally harmful — see the removal trap below.
Removing while iterating
This is the single most-tested bug in the unit.
import java.util.ArrayList;
public class Main {
public static void main(String[] args) {
ArrayList<Integer> a = new ArrayList<>();
for (int v : new int[]{1, 2, 2, 3}) a.add(v);
// Wrong: removing shifts everything left, and i still advances.
for (int i = 0; i < a.size(); i++) {
if (a.get(i) == 2) a.remove(i);
}
System.out.println("forwards, advancing anyway: " + a);
ArrayList<Integer> b = new ArrayList<>();
for (int v : new int[]{1, 2, 2, 3}) b.add(v);
// Right: walk backwards, so a shift cannot move anything unvisited.
for (int i = b.size() - 1; i >= 0; i--) {
if (b.get(i) == 2) b.remove(i);
}
System.out.println("backwards: " + b);
}
}The first loop leaves a 2 behind. Removing index 1 shifts the second 2 down
into index 1, then i++ moves to index 2 — stepping straight over it.
Two fixes, both acceptable on the exam:
- Walk backwards. Shifts only affect indices you have already passed.
- Do not increment when you remove.
if (...) a.remove(i); else i++;
import java.util.ArrayList;
public class Main {
public static void main(String[] args) {
ArrayList<Integer> a = new ArrayList<>();
for (int v : new int[]{10, 20, 30}) a.add(v);
ArrayList<Integer> b = new ArrayList<>(a);
a.remove(2); // by index: drops 30
b.remove(Integer.valueOf(20)); // by value: drops 20
System.out.println("remove(2) -> " + a);
System.out.println("remove(Integer.valueOf(20)) -> " + b);
}
}Which to use
| Use an array when | Use an ArrayList when |
|---|---|
| the size is known and fixed | the size changes |
| the elements are primitives and speed matters | you need add, remove, contains |
| you want 2-D structure | you want to grow from empty |
For the exam, the honest answer is usually “whichever the question hands you”. Both appear, and FRQ 3 is specifically about one or the other.