Java-specific arrays
The Java library tools for arrays: Arrays.copyOf, Arrays.fill, System.arraycopy, Arrays.equals, deep array methods and ArrayList's resizing behaviour, with the semantics and the traps of each.
Learning goals
- Choose the right copy and fill method
- Compare arrays by content
- Handle nested arrays with the deep methods
- Explain how ArrayList resizes
Prerequisites
Arrays.copyOf
Arrays.copyOf(a, newLength) returns a new array of the given length: it pads with zeros (or null, false) when longer and truncates when shorter. copyOfRange(a, from, to) copies a slice, with to exclusive and allowed to exceed the length. Both are O(newLength).
Use them to grow a buffer or take a prefix or slice.
1int[] a = {1, 2, 3, 4, 5};2int[] longer = Arrays.copyOf(a, 7); // {1, 2, 3, 4, 5, 0, 0}3int[] prefix = Arrays.copyOf(a, 3); // {1, 2, 3}4int[] slice = Arrays.copyOfRange(a, 1, 4); // {2, 3, 4}
Arrays.fill
Arrays.fill(a, value) sets every element; Arrays.fill(a, from, to, value) sets a range with to exclusive. Common uses: initialise a distance array to a large value, or reset a counter array between test cases. For arrays of arrays, fill each row separately.
Do not fill a 2D array with one shared row: all rows would be the same object.
1int[] dist = new int[6];2Arrays.fill(dist, Integer.MAX_VALUE / 2); // large, but adding two of them cannot overflow3int[][] grid = new int[3][4];4for (int[] row : grid) Arrays.fill(row, -1); // each row separately
System.arraycopy
System.arraycopy(src, srcPos, dest, destPos, length) is a native block copy. It works correctly when source and destination are the same array with overlapping ranges, which makes it the tool for shifting elements in insert and delete. It throws if a range is out of bounds.
It is typically faster than a hand-written copy loop.
1int[] a = {1, 2, 3, 4, 5, 0};2System.arraycopy(a, 2, a, 3, 3); // shift a[2..4] right by one (overlap is safe)3a[2] = 99; // {1, 2, 99, 3, 4, 5}
Arrays.equals
a.equals(b) on an array compares references, so two arrays with the same contents are not equal. Arrays.equals(a, b) compares length and elements. For nested arrays use Arrays.deepEquals. Arrays.hashCode pairs with Arrays.equals for use as a hash key, though a List or record is usually safer.
Printing an array directly shows its identity, not its values.
1int[] a = {1, 2, 3}, b = {1, 2, 3};2boolean ref = a.equals(b); // false: different objects3boolean val = Arrays.equals(a, b); // true4String text = Arrays.toString(a); // "[1, 2, 3]"5int cmp = Arrays.compare(a, new int[]{1, 2, 4}); // negative: a is smaller
Deep arrays
Arrays of arrays need the deep variants: Arrays.deepToString, Arrays.deepEquals and Arrays.deepHashCode. clone() on a 2D array copies only the row references (a shallow copy); build a deep copy row by row.
Step through the shallow and deep copy cases in the animation.
1int[][] g = {{1, 2}, {3, 4}};2System.out.println(Arrays.deepToString(g)); // [[1, 2], [3, 4]]3int[][] deep = new int[g.length][];4for (int i = 0; i < g.length; i++) deep[i] = g[i].clone();5boolean same = Arrays.deepEquals(g, deep); // true: equal contents, independent rows
Aliasing and copying arrays
Interactive · edit the inputvariables
@1 int[]
new int[] creates one object on the heap; the variable a holds only a reference to it.
1int[] a = { 10, 20, 30, 40 }; // options 0 and 1 use this 1D array2int[] b = a; // option 0: copies the reference only3int[] b = a.clone(); // option 1: copies the elements4int[][] a2 = { { 10, 20 }, { 30, 40 } }; // options 2 and 3 use this 2D array5int[][] b2 = a2.clone(); // option 2: new outer array, SAME row objects6int[][] b2 = Arrays.stream(a2).map(int[]::clone).toArray(int[][]::new); // option 3: rows copied too7b[idx] = v; // (b2[0][idx] = v for options 2 and 3)8System.out.println(Arrays.toString(a)); // does a see the change?
Live state
a.length
4
Execution trace (dry run table)Show 4 stepsHide
| Step | Line | State | What happens |
|---|---|---|---|
| 1 | L1 | a.length=4 | new int[] creates one object on the heap; the variable a holds only a reference to it. |
| 2 | L2 | same=true | b = a copies the reference, not the array. Both variables now point at the same object (@1). |
| 3 | L7 | idx=1 v=99 | b[1] = 99 writes into the object b points at. |
| 4 | L8 | a=[10, 99, 30, 40] b=[10, 99, 30, 40] | a shows the change too ([10, 99, 30, 40]), because a and b are two names for one object. |
ArrayList resizing and amortization
ArrayList keeps an Object[] buffer. An empty list starts with no buffer; the first add allocates 10 slots, and when the buffer is full it grows by about 50%: newCapacity = old + (old >> 1), giving 10, 15, 22, 33, ... Each growth copies the existing references, so n adds cost O(n) in total and every add is amortised O(1). ensureCapacity and the ArrayList(int) constructor avoid the intermediate copies; trimToSize releases the spare room.
Choose the ArrayList preset in the animation to see this growth sequence.
1List<Integer> xs = new ArrayList<>();2for (int i = 0; i < 100; i++) xs.add(i); // resizes at sizes 10, 15, 22, 33, 49, 733List<Integer> ys = new ArrayList<>(100); // one allocation, no resizes during the loop4for (int i = 0; i < 100; i++) ys.add(i);
Dynamic array: capacity, growth and shrinking
Interactive · edit the inputsize elements in the buffer · length 0
data[] · capacity 1
A new dynamic array starts with capacity 1 and no elements.
1void add(int x) {2if (size == data.length) { // buffer is full3data = Arrays.copyOf(data, newCapacity()); // allocate bigger, copy size elements4}5data[size++] = x;6}7int removeLast() {8int x = data[--size];9if (size <= data.length / 4) data = Arrays.copyOf(data, Math.max(1, data.length / 2)); // optional shrink10return x;11}
Live state
size
0
capacity
1
Execution trace (dry run table)Show 21 stepsHide
| Step | Line | State | What happens |
|---|---|---|---|
| 1 | L1 | size=0 capacity=1 | A new dynamic array starts with capacity 1 and no elements. |
| 2 | L2 | size=0 capacity=1 x=4 | size 0 < capacity 1: there is a spare slot, so adding is O(1). |
| 3 | L5 | size=1 capacity=1 x=4 | Write 4 into data[0] and increase size to 1. |
| 4 | L2 | size=1 capacity=1 x=8 | size 1 equals capacity 1: no room for 8, so the buffer must grow. |
| 5 | L3 | size=1 capacity=2 | Allocate capacity 2 and copy the 1 element (1 copy so far). |
| 6 | L5 | size=2 capacity=2 x=8 | Write 8 into data[1] and increase size to 2. |
| 7 | L2 | size=2 capacity=2 x=15 | size 2 equals capacity 2: no room for 15, so the buffer must grow. |
| 8 | L3 | size=2 capacity=4 | Allocate capacity 4 and copy the 2 elements (3 copys so far). |
| 9 | L5 | size=3 capacity=4 x=15 | Write 15 into data[2] and increase size to 3. |
| 10 | L2 | size=3 capacity=4 x=16 | size 3 < capacity 4: there is a spare slot, so adding is O(1). |
| 11 | L5 | size=4 capacity=4 x=16 | Write 16 into data[3] and increase size to 4. |
| 12 | L2 | size=4 capacity=4 x=23 | size 4 equals capacity 4: no room for 23, so the buffer must grow. |
| 13 | L3 | size=4 capacity=8 | Allocate capacity 8 and copy the 4 elements (7 copys so far). |
| 14 | L5 | size=5 capacity=8 x=23 | Write 23 into data[4] and increase size to 5. |
| 15 | L2 | size=5 capacity=8 x=42 | size 5 < capacity 8: there is a spare slot, so adding is O(1). |
| 16 | L5 | size=6 capacity=8 x=42 | Write 42 into data[5] and increase size to 6. |
| 17 | L2 | size=6 capacity=8 x=7 | size 6 < capacity 8: there is a spare slot, so adding is O(1). |
| 18 | L5 | size=7 capacity=8 x=7 | Write 7 into data[6] and increase size to 7. |
| 19 | L2 | size=7 capacity=8 x=9 | size 7 < capacity 8: there is a spare slot, so adding is O(1). |
| 20 | L5 | size=8 capacity=8 x=9 | Write 9 into data[7] and increase size to 8. |
| 21 | L11 | size=8 capacity=8 copies=7 | Final: size 8, capacity 8 (0 spare slots), 3 resizes that copied 7 elements in total. Doubling keeps total copies below 2n. |
Practice ladder
Now try it yourself
Easy
- Create a copy of {1, 2, 3} that is 5 long with copyOf
- Why is
a.equals(b)false for two arrays holding the same values?
Medium
- Insert a value at index 2 of an int array using System.arraycopy
- Explain why Arrays.fill on int[][] with one row object is a bug
Advanced
- List the ArrayList capacities after 1, 11, 16, 23 and 34 adds
Key takeaways
- copyOf grows or truncates, copyOfRange slices, fill initialises, arraycopy shifts safely.
- Compare arrays with Arrays.equals or deepEquals, never with equals or ==.
- ArrayList allocates 10 on first add and grows by 50%; presize when you can.