Collections Framework · Lesson 74 of 150
ArrayList
Understand Java ArrayList: how the hidden array starts at 10, grows by 1.5x, what add, get and remove cost, and when to choose LinkedList instead.
A small bakery takes cake orders on a whiteboard. On a quiet Monday there are four orders. On the Saturday before a festival there are sixty. The owner never knows the number in advance, so a fixed grid of ten boxes would either waste space or run out by noon. What she needs is a list that simply grows when more orders arrive.
Plain Java arrays cannot do that, because their size is fixed the moment you create them. ArrayList solves this. Below you will learn how to use it, what happens inside when it grows, how much each operation costs, and when a LinkedList is the better pick.
What is ArrayList?
Here is what that means in practice:
- It remembers the order in which you added items.
- Index 0 is the first item, just like an array.
- The same value can appear many times.
- It holds objects only, so
intvalues are stored asIntegerthrough autoboxing. - It is not synchronized, so it is not safe for many threads writing at once.
Why is ArrayList used?
Most programs deal with groups of things whose count changes: items in a cart, rows from a file, orders for the day. An ArrayList handles this for you.
- It grows by itself. You never pick a size or copy arrays by hand.
- Reading is fast.
get(i)jumps straight to positioni, however long the list is. - Rich methods.
add,remove,contains,indexOf,sortand many more come ready to use. - It works with the rest of Java. Loops, streams,
Collections.sortand most library methods accept aList.
Because of this, ArrayList is the list class Java developers reach for first.
Real-Life Example
Picture the bakery's display rack with numbered slots. Trays go into the next free slot, and a customer can say "slot 3, please" and get it at once. When the rack is full, the baker brings a bigger rack about one and a half times the size and moves every tray across in the same order.
If the baker squeezes a new tray into slot 1, every tray from slot 1 onward must slide one place to the right. Taking a tray out from the middle makes the ones behind it slide left.
That is ArrayList exactly: slots are indexes, the rack is the internal array, the bigger rack is growth, and sliding trays is the cost of inserting or removing in the middle.
Syntax
javaList<Type> name = new ArrayList<>(); // empty List<Type> big = new ArrayList<>(500); // room for 500 name.add(item); // append at the end name.add(index, item); // insert at a position Type t = name.get(index); // read by index name.set(index, item); // replace by index name.remove(index); // delete by index name.size(); // number of items
- The variable type is
List, the interface, so the code does not depend on the exact class. <Type>must be a class such asStringorInteger, never a primitive likeint.- The constructor with a number sets the starting capacity, which saves resizes when you know the size.
addwith one argument appends; with two, it inserts and shifts later items right.getandsetwork by index and throwIndexOutOfBoundsExceptionfor a bad index.size()is the count of items, not the length of the hidden array.
How ArrayList Works
Inside every ArrayList is a plain Object[] array called elementData, plus a size counter. The array is usually larger than size, and the spare room is what makes adding cheap.
textnew ArrayList<>() elementData = {} (empty) | v first add: capacity 10 [a|b|c|d|e|f|g|h|i|j] | v 11th add: array is full new = 10 + (10 >> 1) = 15 | v copy 10 items across old array -> garbage
The no-argument constructor does not build ten slots straight away. It waits for the first add, then creates an array of 10, the default capacity. When an add finds the array full, Java makes a new one 1.5 times as big, copies the old items over with System.arraycopy, and lets the old array be collected. So the capacity grows 10, 15, 22, 33, 49 and so on.
Copying sounds slow, but it happens rarely. Averaged over many adds, appending costs O(1). This is called amortized constant time.
The real cost is in the middle of the list. Inserting at index 1 shifts every later item one step right. Removing from index 1 shifts them left. With a million items, that is up to a million moves.
textget(i) / set(i) O(1) add(item) at end O(1)* add(i, item) O(n) remove(i) O(n) contains / indexOf O(n) * amortized
Example Programs
Taking cake orders
javaimport java.util.ArrayList; import java.util.List; public class CakeOrders { public static void main(String[] args) { List<String> orders = new ArrayList<>(); orders.add("Plum"); orders.add("Brownie"); orders.add("Tart"); System.out.println(orders); orders.add(1, "Rusk"); System.out.println("After insert:"); System.out.println(orders); orders.set(2, "Nut brownie"); System.out.println("Order 2: " + orders.get(2)); orders.remove("Plum"); System.out.println("Size now: " + orders.size()); System.out.println("Tart at: " + orders.indexOf("Tart")); } }
Output:
text[Plum, Brownie, Tart] After insert: [Plum, Rusk, Brownie, Tart] Order 2: Nut brownie Size now: 3 Tart at: 2
Inserting "Rusk" at index 1 pushed "Brownie" to index 2. Removing "Plum" from the front shifted all the others left, so "Tart" moved from index 3 to index 2.
Watching the capacity grow
javapublic class RackGrowth { public static void main(String[] args) { int capacity = 10; System.out.println("Start: 10 slots"); for (int size = 1; size <= 60; size++) { if (size > capacity) { capacity = capacity + (capacity >> 1); System.out.println("Item " + size + " -> " + capacity + " slots"); } } } }
Output:
textStart: 10 slots Item 11 -> 15 slots Item 16 -> 22 slots Item 23 -> 33 slots Item 34 -> 49 slots Item 50 -> 73 slots
This program copies the growth rule ArrayList uses. Sixty adds need only five copies, which is why appending stays cheap on average.
Removing by index or by value
javaimport java.util.ArrayList; import java.util.List; public class TrayCleanup { public static void main(String[] args) { List<Integer> trays = new ArrayList<>(); trays.add(5); trays.add(0); trays.add(12); trays.add(0); trays.add(8); trays.remove(1); System.out.println("remove(1): " + trays); trays.remove(Integer.valueOf(12)); System.out.println("remove(12): " + trays); trays.removeIf(count -> count == 0); System.out.println("No empties: " + trays); } }
Output:
textremove(1): [5, 12, 0, 8] remove(12): [5, 0, 8] No empties: [5, 8]
With a list of Integer, remove(1) deletes the item at index 1, not the value 1. To remove a value, pass an Integer object. removeIf is the safe way to delete every item that matches a rule.
ArrayList vs LinkedList
| Point | ArrayList | LinkedList |
|---|---|---|
| Inside | Resizable array | Doubly linked nodes |
| get(i) | O(1) | O(n), walks nodes |
| Add at end | O(1) amortized | O(1) |
| Insert in middle | O(n), shifts items | O(1) once there, O(n) to reach |
| Memory per item | Low | Higher, two links each |
| Best for | Reading by index | Adding at both ends |
In real projects ArrayList wins most of the time, because its items sit together in memory and the CPU reads them quickly. Choose LinkedList or ArrayDeque when you mostly add and remove at the ends.
Common Mistakes
- Mixing up `remove(int)` and `remove(Object)`. On a list of
Integer,remove(2)removes index 2. Writeremove(Integer.valueOf(2))for the value. - Confusing size with capacity.
size()counts items. The capacity is hidden and has no getter. - Growing `Arrays.asList`. It returns a fixed-size list, so
addthrowsUnsupportedOperationException. Wrap it innew ArrayList<>(...)first. - Reading past the end. Valid indexes run from 0 to
size() - 1.
Interview Questions
What is the default capacity of an ArrayList, and how does it grow?
Ans:The no-argument constructor starts with an empty array and allocates 10 slots on the first add. When full, it grows to about 1.5 times the old size using old + (old >> 1), then copies the items across.
Why is get() fast but add(0, item) slow?
Ans:get reads the array slot directly, so it is O(1). Adding at index 0 must shift every existing item one place right, which is O(n).
What is the difference between size and capacity?
Ans:Size is the number of items stored. Capacity is the length of the internal array, which is usually larger so that future adds need no copying.
How do you remove items while iterating?
Ans:Use list.removeIf(condition) or call remove() on an explicit Iterator. Removing through the list inside a for-each loop throws ConcurrentModificationException.
When would you pick LinkedList over ArrayList?
Ans:When most operations add or remove at the start or end and you rarely read by index. For random access and general use, ArrayList is faster and uses less memory.
Summary
ArrayListis a resizable array that keeps insertion order and allows duplicates.- It allocates 10 slots on the first add and grows by 1.5 times when full.
getandsetare O(1); adding at the end is amortized O(1).- Inserting or removing in the middle is O(n) because items shift.
- Use
removeIfor anIteratorto delete while looping. - Prefer it over
LinkedListunless you mainly work at the list's ends.
Related Topics
- List: the interface
ArrayListimplements. - LinkedList: the node-based alternative from the table.
- Iterator: the safe way to walk and remove items.
- Arrays: the fixed-size structure hidden inside.
Practice Problems
Try each problem on your own first, then compare with the solution.
Easy: Flip the Display Rack
The bakery rack holds Bun, Rusk, Tart, Puff and Scone, in that order. The baker wants the rack reversed for the evening. Reverse the ArrayList in place using only get and set, without Collections.reverse, and print it before and after.
Expected output:
textBefore: [Bun, Rusk, Tart, Puff, Scone] After: [Scone, Puff, Tart, Rusk, Bun]
Show answerHide answer
size() / 2 swaps, and each get or set is O(1), so the whole reverse is O(n) with no extra list.javaimport java.util.ArrayList; import java.util.Arrays; import java.util.List; public class FlipRack { public static void main(String[] args) { List<String> rack = new ArrayList<>(Arrays.asList( "Bun", "Rusk", "Tart", "Puff", "Scone")); System.out.println("Before:"); System.out.println(rack); int left = 0; int right = rack.size() - 1; while (left < right) { String temp = rack.get(left); rack.set(left, rack.get(right)); rack.set(right, temp); left++; right--; } System.out.println("After:"); System.out.println(rack); } }
Output:
textBefore: [Bun, Rusk, Tart, Puff, Scone] After: [Scone, Puff, Tart, Rusk, Bun]
Medium: Merge Morning and Evening Tokens
Order tokens from the morning shift are 3, 8, 15, 21 and from the evening shift are 5, 8, 19, 30, 42. Both lists are already sorted. Merge them into one sorted ArrayList in a single pass, keeping duplicates, and print it with its size.
Expected output:
text[3, 5, 8, 8, 15, 19, 21, 30, 42] Tokens: 9
Show answerHide answer
javaimport java.util.ArrayList; import java.util.Arrays; import java.util.List; public class MergeTokens { public static void main(String[] args) { List<Integer> morning = Arrays.asList(3, 8, 15, 21); List<Integer> evening = Arrays.asList(5, 8, 19, 30, 42); List<Integer> all = new ArrayList<>(morning.size() + evening.size()); int i = 0; int j = 0; while (i < morning.size() && j < evening.size()) { if (morning.get(i) <= evening.get(j)) { all.add(morning.get(i++)); } else { all.add(evening.get(j++)); } } while (i < morning.size()) all.add(morning.get(i++)); while (j < evening.size()) all.add(evening.get(j++)); System.out.println(all); System.out.println("Tokens: " + all.size()); } }
Output:
text[3, 5, 8, 8, 15, 19, 21, 30, 42] Tokens: 9