When I first met arrays in Java, the basics felt easy: put some values in a row, pick one by its number. Then came int[][], and my brain got stuck. Is it a table? A grid? Why can some rows be shorter than others? And why did my for-each loop refuse to change my values?
All of those questions have one answer: you need to know how Java actually stores arrays. Once you see that, 1D arrays, 2D arrays, jagged arrays and for-each all start to feel like one idea instead of four.
Unless I show a full class, the snippets below go inside a main method. Some of them use Arrays, so add import java.util.Arrays; at the top of your file.
What is an array?
An array is a fixed-size box of slots. Every slot holds the same type of value, and each slot has a number called an index. Indexes start at 0, not 1.
int[] scores = {80, 92, 75};
System.out.println(scores[1]);
System.out.println(scores.length);
Output:
92
3
Two things to notice:
-
scores[1]is the second value, because counting starts at 0. -
scores.lengthhas no brackets. It's a field, not a method. This trips up many beginners, because strings uselength()and lists usesize().
Three ways to create an array
int[] a = new int[3]; // 3 slots, filled with default values
int[] b = {10, 20, 30}; // shortcut when you know the values
int[] c = new int[]{10, 20, 30}; // same thing, written longer
When you use new int[3] without values, Java fills every slot with a default:
-
0for numbers -
falsefor booleans -
nullfor objects likeString
int[] marks = new int[3];
String[] names = new String[2];
System.out.println(marks[0]);
System.out.println(names[0]);
Output:
0
null
What happens behind the scenes
An array is an object. When you write new int[3], the JVM creates it on the heap, remembers its length, and gives you a reference (an address) that you store in your variable.
STACK HEAP
+---------+ +-----------------+
| scores -|--------> | length = 3 |
+---------+ | [0] [1] [2] |
| 80 92 75 |
+-----------------+
Because the JVM knows the length, it checks every index you use. If you go too far, you don't corrupt memory like you might in C. You get a clear error instead:
int[] scores = {80, 92, 75};
System.out.println(scores[3]);
Output:
Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: Index 3 out of bounds for length 3
The valid indexes here are 0, 1 and 2. The last valid index is always length - 1.
Looping through an array
int[] scores = {80, 92, 75};
int total = 0;
for (int i = 0; i < scores.length; i++) {
total += scores[i];
}
System.out.println("Total: " + total);
Output:
Total: 247
Notice i < scores.length and not <=. Using <= would try to read scores[3] and crash.
Two array facts that surprise people
1. Printing an array directly gives gibberish.
int[] nums = {1, 2, 3};
System.out.println(nums);
System.out.println(Arrays.toString(nums));
Output (the first line will look a bit different on your machine):
[I@6d06d69c
[1, 2, 3]
The first line is the type ([I means "array of int") plus a code for the object. Use Arrays.toString() to see the real values.
2. Assigning an array copies the reference, not the array.
int[] a = {1, 2, 3};
int[] b = a;
b[0] = 99;
System.out.println(a[0]);
Output:
99
Both a and b point to the same array on the heap. If you want a real copy, use Arrays.copyOf(a, a.length) or a.clone().
2D arrays: an array of arrays
Now the part that confused me. A 2D array looks like a table with rows and columns:
int[][] grid = {
{1, 2, 3},
{4, 5, 6}
};
System.out.println(grid.length);
System.out.println(grid[0].length);
System.out.println(grid[1][2]);
Output:
2
3
6
-
grid.lengthis the number of rows (2). -
grid[0].lengthis the number of columns in row 0 (3). -
grid[1][2]means row 1, column 2, which is6.
The big secret: Java has no real "2D" array
In Java, int[][] is an array whose slots each hold a reference to another array. In some other languages a 2D array is one flat block of memory. In Java it's arrays inside an array.
grid
+--------+
| ref 0 -|-----> [ 1 | 2 | 3 ]
| ref 1 -|-----> [ 4 | 5 | 6 ]
+--------+
(outer array) (row arrays, each its own object)
So grid[0] is not a number. It's a whole int[], and grid[0][1] just means "take row 0, then take slot 1 of that row."
Once you see this, grid.length and grid[0].length make perfect sense. They're just the length of two different arrays.
Looping through a 2D array
You need two loops: the outer one walks the rows, the inner one walks the columns of the current row.
int[][] grid = {
{1, 2, 3},
{4, 5, 6}
};
for (int i = 0; i < grid.length; i++) {
for (int j = 0; j < grid[i].length; j++) {
System.out.print(grid[i][j] + " ");
}
System.out.println();
}
Output:
1 2 3
4 5 6
Look at the inner loop's limit: grid[i].length, not grid[0].length. Using the current row's length is safer, and it's what makes the next topic possible.
Creating a 2D array with a size
int[][] table = new int[2][3]; // 2 rows, 3 columns, all zeros
table[1][2] = 7;
System.out.println(Arrays.deepToString(table));
Output:
[[0, 0, 0], [0, 0, 7]]
For 2D arrays, Arrays.toString() would show the row addresses, so use Arrays.deepToString() instead.
Jagged arrays: rows of different lengths
Since each row is its own array, nothing forces the rows to be the same size. An array whose rows have different lengths is called a jagged array.
Here's one that builds a small triangle:
int[][] triangle = new int[3][]; // 3 rows, row sizes not set yet
for (int i = 0; i < triangle.length; i++) {
triangle[i] = new int[i + 1]; // row i gets i+1 slots
for (int j = 0; j < triangle[i].length; j++) {
triangle[i][j] = j + 1;
}
}
for (int[] row : triangle) {
System.out.println(Arrays.toString(row));
}
Output:
[1]
[1, 2]
[1, 2, 3]
Look at the first line: new int[3][]. We gave the number of rows but left the column count empty. Java creates the outer array and fills it with null. We then create each row ourselves, with whatever size we like.
triangle
+--------+
| ref 0 -|-----> [ 1 ]
| ref 1 -|-----> [ 1 | 2 ]
| ref 2 -|-----> [ 1 | 2 | 3 ]
+--------+
You can also write a jagged array directly:
int[][] days = {
{5, 6}, // week 1: only 2 entries
{7, 8, 9, 10}, // week 2: 4 entries
{3} // week 3: 1 entry
};
Why use it? When your data isn't a neat rectangle. Think of the number of subjects each student takes, or the number of orders per customer. A normal 2D array would waste memory on empty slots. A jagged array uses exactly what you need.
The one trap: if you skip creating a row and then use it, you hit null.
int[][] x = new int[2][];
x[0][0] = 5; // NullPointerException, row 0 was never created
The for-each loop (and its variable)
The enhanced for loop, also called for-each, is the tidy way to visit every item without caring about indexes.
int[] scores = {80, 92, 75};
for (int s : scores) {
System.out.println(s);
}
Output:
80
92
75
Read the : as "in": "for each s in scores". The variable s is called the loop variable, and it's the part people misunderstand.
The loop variable is a copy
On each round, Java copies the current value into s. It does not give you the slot itself. So changing s does nothing to the array:
int[] nums = {1, 2, 3};
for (int n : nums) {
n = n * 10;
}
System.out.println(Arrays.toString(nums));
Output:
[1, 2, 3]
The array didn't change, because we only changed the copy. To really change the values, use an indexed loop:
for (int i = 0; i < nums.length; i++) {
nums[i] = nums[i] * 10;
}
System.out.println(Arrays.toString(nums));
Output:
[10, 20, 30]
What about arrays of objects?
For objects, the copy is a copy of the reference. So you can change what the object contains, but you can't swap the object in the array:
StringBuilder[] items = { new StringBuilder("a"), new StringBuilder("b") };
for (StringBuilder sb : items) {
sb.append("!"); // changes the real object
}
for (StringBuilder sb : items) {
sb = new StringBuilder("x"); // only re-points the copy, no effect
}
System.out.println(items[0] + " " + items[1]);
Output:
a! b!
For-each on a 2D array
Remember that a 2D array is an array of arrays. So the outer loop variable is a whole row, and you need a second loop for the values inside it:
int[][] grid = {
{1, 2, 3},
{4, 5, 6}
};
int sum = 0;
for (int[] row : grid) {
for (int value : row) {
sum += value;
}
}
System.out.println(sum);
Output:
21
Notice the outer variable's type is int[], not int. This is the "array of arrays" idea showing up again, and it works the same for jagged arrays, since each row brings its own length.
What happens behind the scenes
For arrays, the compiler quietly rewrites your for-each into a normal indexed loop. Your loop:
for (int n : nums) {
// use n
}
becomes roughly:
int[] copyOfRef = nums;
int len = copyOfRef.length;
for (int i = 0; i < len; i++) {
int n = copyOfRef[i];
// use n
}
That explains everything: n is a fresh variable filled from the slot on each round, so changing it can't touch the array. It also explains why for-each has no index. The index exists, but the compiler hides it from you.
Which one should I use?
-
Array or ArrayList? Use an array when the size is fixed and known. Use an
ArrayListwhen the size changes while the program runs, because arrays can't grow. - 2D array or jagged array? Use a regular 2D array for rectangular data like a chessboard, a seat map or an image. Use a jagged array when rows naturally have different lengths.
-
for or for-each? Use for-each when you only need to read every item. Use a normal
forwhen you need the index, want to go backwards or skip items, or need to change the values in the array. -
Printing: use
Arrays.toString()for 1D andArrays.deepToString()for 2D.
Common mistakes
-
Using
<=instead of<in the loop.i <= arr.lengthgoes one step too far and throwsArrayIndexOutOfBoundsException. The last valid index islength - 1. -
Printing an array directly. You get
[I@6d06d69cinstead of your values. UseArrays.toString(). -
Thinking
b = acopies the array. It copies only the reference. Both names control one array. UseArrays.copyOf()orclone()for a real copy. -
Comparing arrays with
==. It checks whether they're the same object, not whether the values match. UseArrays.equals()(orArrays.deepEquals()for 2D). -
Using a jagged row before creating it.
new int[3][]leaves every row asnull, so touching one gives aNullPointerException. - Expecting for-each to change the array. The loop variable is a copy. Use an indexed loop when you need to write values back.
Quick recap
- An array is a fixed-size row of same-type slots, stored as an object on the heap, with indexes from 0.
- Use
length(no brackets) for arrays. The JVM checks every index and throws a clear error if you go out of range. - A 2D array is really an array of arrays: the outer array holds references to the row arrays.
- A jagged array is a 2D array whose rows have different lengths. Rows you haven't created yet are
null. - For-each gives you a copy of each item, and the compiler turns it into a normal indexed loop behind the scenes.
- For a 2D array, the outer for-each variable is a whole row (
int[]), not a single number.
Try it yourself
Here's a small practice idea: store the marks of three students, where each student took a different number of subjects. Use a jagged array, then use a nested for-each to print each student's average. After that, try changing a value inside a for-each and see what happens. Then fix it with an indexed loop.
If you want to go one step further, write your own printGrid method that works for any 2D or jagged array. It's a great way to prove to yourself that you've understood "array of arrays."
If this helped, or if you spotted something I should fix, tell me in the comments. I'm still learning too.
Top comments (0)