DEV Community

Mayur Bodhare
Mayur Bodhare

Posted on

Java Arrays: What Really Sits in Memory

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);
Enter fullscreen mode Exit fullscreen mode

Output:

92
3
Enter fullscreen mode Exit fullscreen mode

Two things to notice:

  • scores[1] is the second value, because counting starts at 0.
  • scores.length has no brackets. It's a field, not a method. This trips up many beginners, because strings use length() and lists use size().

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
Enter fullscreen mode Exit fullscreen mode

When you use new int[3] without values, Java fills every slot with a default:

  • 0 for numbers
  • false for booleans
  • null for objects like String
int[] marks = new int[3];
String[] names = new String[2];

System.out.println(marks[0]);
System.out.println(names[0]);
Enter fullscreen mode Exit fullscreen mode

Output:

0
null
Enter fullscreen mode Exit fullscreen mode

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     |
                       +-----------------+
Enter fullscreen mode Exit fullscreen mode

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]);
Enter fullscreen mode Exit fullscreen mode

Output:

Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: Index 3 out of bounds for length 3
Enter fullscreen mode Exit fullscreen mode

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);
Enter fullscreen mode Exit fullscreen mode

Output:

Total: 247
Enter fullscreen mode Exit fullscreen mode

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));
Enter fullscreen mode Exit fullscreen mode

Output (the first line will look a bit different on your machine):

[I@6d06d69c
[1, 2, 3]
Enter fullscreen mode Exit fullscreen mode

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]);
Enter fullscreen mode Exit fullscreen mode

Output:

99
Enter fullscreen mode Exit fullscreen mode

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]);
Enter fullscreen mode Exit fullscreen mode

Output:

2
3
6
Enter fullscreen mode Exit fullscreen mode
  • grid.length is the number of rows (2).
  • grid[0].length is the number of columns in row 0 (3).
  • grid[1][2] means row 1, column 2, which is 6.

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)
Enter fullscreen mode Exit fullscreen mode

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();
}
Enter fullscreen mode Exit fullscreen mode

Output:

1 2 3 
4 5 6 
Enter fullscreen mode Exit fullscreen mode

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));
Enter fullscreen mode Exit fullscreen mode

Output:

[[0, 0, 0], [0, 0, 7]]
Enter fullscreen mode Exit fullscreen mode

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));
}
Enter fullscreen mode Exit fullscreen mode

Output:

[1]
[1, 2]
[1, 2, 3]
Enter fullscreen mode Exit fullscreen mode

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 ]
  +--------+
Enter fullscreen mode Exit fullscreen mode

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
};
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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);
}
Enter fullscreen mode Exit fullscreen mode

Output:

80
92
75
Enter fullscreen mode Exit fullscreen mode

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));
Enter fullscreen mode Exit fullscreen mode

Output:

[1, 2, 3]
Enter fullscreen mode Exit fullscreen mode

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));
Enter fullscreen mode Exit fullscreen mode

Output:

[10, 20, 30]
Enter fullscreen mode Exit fullscreen mode

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]);
Enter fullscreen mode Exit fullscreen mode

Output:

a! b!
Enter fullscreen mode Exit fullscreen mode

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);
Enter fullscreen mode Exit fullscreen mode

Output:

21
Enter fullscreen mode Exit fullscreen mode

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
}
Enter fullscreen mode Exit fullscreen mode

becomes roughly:

int[] copyOfRef = nums;
int len = copyOfRef.length;
for (int i = 0; i < len; i++) {
    int n = copyOfRef[i];
    // use n
}
Enter fullscreen mode Exit fullscreen mode

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 ArrayList when 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 for when 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 and Arrays.deepToString() for 2D.

Common mistakes

  1. Using <= instead of < in the loop. i <= arr.length goes one step too far and throws ArrayIndexOutOfBoundsException. The last valid index is length - 1.
  2. Printing an array directly. You get [I@6d06d69c instead of your values. Use Arrays.toString().
  3. Thinking b = a copies the array. It copies only the reference. Both names control one array. Use Arrays.copyOf() or clone() for a real copy.
  4. Comparing arrays with ==. It checks whether they're the same object, not whether the values match. Use Arrays.equals() (or Arrays.deepEquals() for 2D).
  5. Using a jagged row before creating it. new int[3][] leaves every row as null, so touching one gives a NullPointerException.
  6. 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)