skip to content

arrays

fixed-size boxes in a row. 2d arrays are arrays of arrays (literally).

chapter
chapter 02.08
reading time
6 min read

an array is a fixed-size row of slots, all of one type, numbered from 0. it's an object on the heap, so the variable holds a reference to it. you can't grow one; you make a new one and copy. and a 2d array is literally an array of arrays, which is why its rows can have different lengths.

the picture

a row of numbered lockers, all the same size, bolted together. you get to pick how many when you build it, and that number is final. locker numbers start at 0, so a row of 5 goes from 0 to 4.

java
int[] scores = {90, 72, 85};
String[] names = {"ada", "linus"};
stack
main
scores
names
heap
int[]
900
721
852
String[]
0
1
String
"ada"
String
"linus"
both arrays live on the heap. an int[] holds the ints themselves. a String[] holds references, one arrow per slot, because strings are objects too.

making and using one

Main.java
void main() {
    int[] a = {3, 1, 4, 1, 5};
    IO.println(a.length + " " + a[0] + " " + a[a.length - 1]);
    IO.println(Arrays.toString(new int[3]) + " " + Arrays.toString(new String[2]) + " " + Arrays.toString(new boolean[2]));
}
output
5 3 5
[0, 0, 0] [null, null] [false, false]
  • {3, 1, 4} builds and fills one in one go. new int[5] makes an empty one of length 5.
  • an empty array isn't empty, it's zeroed: 0 for numbers, false for booleans, null for references. (fields get the same defaults; local variables don't.)
  • a.length is a field, no parentheses (unlike String.length(), a classic mixup).
  • the last element is a[a.length - 1]. a[a.length] is one past the end.
int[] a → array object #1a.length = 5
3
0
1
1
4
2
1
3
5
4
read a[i] =
int[] a = {3, 1, 4, 1, 5};
→ [3, 1, 4, 1, 5]

five ints in a row. pick an index to read, or run one of the Arrays helpers.

read any index, including -1 and one past the end, then try the Arrays helpers. watch the object number: sort and fill change the array in place, but copyOf hands you a brand new array.

java checks every index, every time. read past the end and you get an exception, never somebody else's memory (that's a whole class of C security bugs that can't happen here):

Main.java
void main() {
    int[] a = new int[5];
    IO.println("reading a[5]...");
    IO.println(a[5]);
}
output
reading a[5]...
Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: Index 5 out of bounds for length 5

printing and comparing

Main.java
void main() {
    int[] a = {3, 1, 4, 1, 5};
    int[] b = Arrays.copyOf(a, 7);
    Arrays.sort(a);
    IO.println(Arrays.toString(a) + " " + Arrays.toString(b));
    int[] d = {1, 2, 3};
    int[] e = {1, 2, 3};
    IO.println((d == e) + " " + d.equals(e) + " " + Arrays.equals(d, e));
}
output
[1, 1, 3, 4, 5] [3, 1, 4, 1, 5, 0, 0]
false false true

two traps in there. println(a) on an array prints something like [I@5a4041cc (the type code for "int array", an @, and a hash), never the contents: use Arrays.toString. and both == and equals on arrays ask "same object?", so two arrays with identical contents aren't "equal". use Arrays.equals.

predict the output
java
int[] a = {1, 2, 3};
for (int x : a) {
    x *= 2;
}
IO.println(Arrays.toString(a));

2d arrays are arrays of arrays

java has no real grid type. int[][] means "an array whose elements are int[]s". the outer array holds references, and each row is a separate array object:

java
int[][] grid = new int[3][4];
grid[1][2] = 9;
stack
main
grid
heap
int[][]
0
1
2
int[]
00
01
02
03
int[]
00
01
92
03
int[]
00
01
02
03
one outer array of 3 references, three separate row arrays of 4 ints. grid[1][2] means: follow slot 1 of the outer array, then read slot 2 of that row.

since each row is its own array, the rows don't have to be the same length. leave the second size out (new int[3][]) and you get the outer array only, rows to be filled in later:

Main.java next to run
  1. 1void main() {
  2. 2 int[][] tri = new int[3][];
  3. 3 tri[0] = new int[] {1};
  4. 4 tri[1] = new int[] {1, 1};
  5. 5 tri[2] = new int[] {1, 2, 1};
  6. 6 int[] row = tri[2];
  7. 7 row[1] = 9;
  8. 8 IO.println(tri[2][1]);
  9. 9}
stdout
·
stack
main
no locals yet
heap
nothing allocated yet.

main starts.

step 1/8
step through building a jagged (triangle-shaped) array row by row. at step 6, row and tri[2] become two arrows to the same row, so a write through one shows up through the other.

Arrays.deepToString(grid) prints nested arrays ([[0, 0, 0, 0], [0, 0, 9, 0], [0, 0, 0, 0]]), since plain toString would print the rows' [I@... addresses.

one level deeper:arrays are covariant, and it can blow up at runtime+

a String[] is a kind of Object[], so this compiles:

java
Object[] o = new String[1];
o[0] = 42;

and then throws ArrayStoreException: java.lang.Integer when it runs, because the array remembers it was created as a String[] and checks every store. that runtime check is why arrays and generics don't mix well, and why List<String> is deliberately not a List<Object>. the whole story is in arrays, varargs and other limits.

keylines
  • an array is a fixed-size, zero-indexed object. the variable holds a reference.
  • new arrays are zeroed: 0, false, null.
  • every index is checked. out of range throws ArrayIndexOutOfBoundsException.
  • print with Arrays.toString, compare with Arrays.equals. == and equals mean "same object".
  • int[][] is an array of row arrays. rows can differ in length.