Arrays, and the Arrays utility class
Fixed length and default values, jagged arrays, why Arrays.sort uses two different algorithms, and the copy and equality methods people get wrong.
An array in Java is an object with a fixed length, and almost every array bug comes from one of those two words.
Declaring and creating
int[] marks; // a reference, currently null
marks = new int[5]; // length 5, all zeros
int[] scores = new int[] {87, 92, 78};
int[] short_form = {87, 92, 78}; // only valid at the declarationint[] marks is the preferred spelling. int marks[] compiles — it is inherited from C — but puts
type information in two places and reads worse.
Elements get a default value, never garbage:
| Element type | Default |
|---|---|
int, long, short, byte |
0 |
double, float |
0.0 |
char |
'\u0000' — the null character, not a space |
boolean |
false |
| any object type | null |
That last row is the source of an everyday NullPointerException:
String[] names = new String[3];
System.out.println(names[0]); // null — printed fine
System.out.println(names[0].length()); // NullPointerExceptionLength is fixed, and it is a field
int[] xs = {1, 2, 3};
System.out.println(xs.length); // 3 — a FIELD, no brackets
System.out.println("abc".length()); // 3 — a METHOD on Stringlength for arrays, length() for strings, size() for collections. Three spellings for one
idea, and it is one of those things you simply have to memorise.
You cannot resize an array. To grow one, allocate a bigger one and copy:
int[] bigger = Arrays.copyOf(xs, xs.length * 2); // extra slots are 0That is exactly what ArrayList does for you internally, which is the answer to "how does
ArrayList grow?" — it holds an array, and when it fills up it allocates a larger one (typically
about 1.5×) and copies.
Indexing out of bounds throws rather than reading adjacent memory:
int[] xs = new int[3];
xs[3] = 1; // ArrayIndexOutOfBoundsException: Index 3 out of bounds for length 3
xs[-1] = 1; // same exceptionThis is a real difference from C, and one of Java's headline safety properties.
Iterating
for (int i = 0; i < xs.length; i++) { // when you need the index
System.out.println(i + ": " + xs[i]);
}
for (int x : xs) { // enhanced for — cleaner when you do not
System.out.println(x);
}The enhanced for gives you a copy of each element for primitives, so assigning to the loop
variable changes nothing:
for (int x : xs) {
x = 0; // modifies the copy — xs is untouched
}To modify the array, you need the index. For an array of objects, the loop variable is a copy of the reference, so calling a mutating method on it does affect the object — the same distinction as passing an object to a method.
Two-dimensional arrays
int[][] grid = new int[3][4]; // 3 rows, 4 columns, all 0
int[][] fixed = {{1, 2}, {3, 4}, {5, 6}};
System.out.println(grid.length); // 3 — number of rows
System.out.println(grid[0].length); // 4 — length of the first row
for (int r = 0; r < grid.length; r++) {
for (int c = 0; c < grid[r].length; c++) {
grid[r][c] = r * 10 + c;
}
}A 2-D array in Java is an array of arrays, not a rectangular block. So rows can have different lengths — a jagged array:
int[][] jagged = new int[3][]; // rows not allocated yet
jagged[0] = new int[2];
jagged[1] = new int[5];
jagged[2] = new int[1];Note grid[r].length rather than grid[0].length inside the loop. It costs nothing and it is
correct for jagged input.
The Arrays utility class
import java.util.Arrays;
int[] xs = {5, 2, 8, 1};
Arrays.sort(xs); // in place, ascending
System.out.println(Arrays.toString(xs)); // [1, 2, 5, 8]
Arrays.fill(xs, 7); // every element 7
int[] part = Arrays.copyOfRange(xs, 1, 3); // indices 1..2
int found = Arrays.binarySearch(xs, 5); // requires a SORTED array
boolean same = Arrays.equals(xs, new int[]{7, 7, 7, 7});
int[][] g = {{1}, {2}};
System.out.println(Arrays.deepToString(g)); // [[1], [2]]Four traps in that short list.
System.out.println(xs) prints garbage. An array does not override toString, so you get
[I@1b6d3586 — the type and a hash. Use Arrays.toString, or Arrays.deepToString for 2-D.
xs == ys compares references. Arrays.equals compares contents element by element, and
Arrays.deepEquals recurses into nested arrays.
Arrays.sort uses two different algorithms, and this is a favourite question:
- For primitives — a dual-pivot quicksort. Average O(n log n), in place, not stable.
Stability is irrelevant for primitives, since two equal
ints are indistinguishable. - For objects — TimSort, a merge sort variant. O(n log n) guaranteed, stable, needs O(n) extra space. Stability matters here, because two objects can be equal by the comparator and still be different objects.
Arrays.sort cannot sort primitives in descending order. There is no comparator overload for
int[]. Your options:
Integer[] boxed = {5, 2, 8};
Arrays.sort(boxed, Collections.reverseOrder()); // works — objects
int[] xs = {5, 2, 8};
Arrays.sort(xs); // ascending, then reverse manually
for (int i = 0, j = xs.length - 1; i < j; i++, j--) {
int t = xs[i]; xs[i] = xs[j]; xs[j] = t;
}Sorting objects by a field, which is the everyday case:
String[] names = {"banana", "kiwi", "apple"};
Arrays.sort(names); // alphabetical
Arrays.sort(names, Comparator.comparingInt(String::length)); // by length
Arrays.sort(names, Comparator.reverseOrder()); // reverse alphabeticalCopying, and the shallow-copy trap
int[] a = {1, 2, 3};
int[] b = a; // NOT a copy — a second reference
int[] c = a.clone(); // a copy
int[] d = Arrays.copyOf(a, a.length); // a copy
int[] e = new int[a.length];
System.arraycopy(a, 0, e, 0, a.length); // a copy, fastest for large arraysb = a gives you two names for one array, exactly as in Python. b[0] = 9 changes a[0].
And clone() on a 2-D array is shallow — it copies the array of row references, not the rows:
int[][] grid = {{1, 2}, {3, 4}};
int[][] copy = grid.clone();
copy[0][0] = 9;
System.out.println(grid[0][0]); // 9 — the original changed
int[][] deep = new int[grid.length][];
for (int r = 0; r < grid.length; r++) {
deep[r] = grid[r].clone(); // copy each row
}Arrays versus ArrayList
| Array | ArrayList |
|
|---|---|---|
| Size | fixed at creation | grows automatically |
| Holds | primitives or objects | objects only (boxing for primitives) |
| Length | xs.length |
list.size() |
| Read/write by index | xs[i] |
get(i) / set(i, v) |
| Insert in the middle | manual shifting | add(i, v), O(n) |
| Memory | lower — no boxing, no headroom | higher |
Use an array when the size is known and the elements are primitives, which in a coding round is
often. Use ArrayList when the size changes.
One conversion trap worth knowing:
Integer[] boxed = {1, 2, 3};
List<Integer> list = Arrays.asList(boxed); // fixed-size VIEW of the array
list.add(4); // UnsupportedOperationException
list.set(0, 9); // allowed — and it changes boxed[0]
List<Integer> real = new ArrayList<>(Arrays.asList(boxed)); // an independent listArrays.asList returns a fixed-size list backed by the array. It is not an ArrayList, and it
does not copy.
What to take into an interview
- An array's length is fixed at creation and read as a field,
xs.length. - Elements default to 0 /
false/'\u0000'/null— never garbage. - Out-of-bounds access throws
ArrayIndexOutOfBoundsException. - A 2-D array is an array of arrays, so it can be jagged. Use
grid[r].length. Arrays.toStringto print,Arrays.equalsto compare contents.Arrays.sortis dual-pivot quicksort for primitives (not stable) and TimSort for objects (stable).- There is no descending sort for
int[]— box it, or reverse afterwards. clone()on a 2-D array is shallow; copy each row.Arrays.asListgives a fixed-size view, not anArrayList.