انتقل إلى المحتوى
أمين أبوزيد
منشورBEG-015-L03

المصفوفات ثنائية الأبعاد

  • الدرس 3 من 8
  • 10 دقيقة قراءة
  • مبتدئ
في هذا الدرس 11 موضوعات

هذا الدرس متاح حاليًا باللغة الإنجليزية فقط، ولم تُنشر نسخته العربية بعد.

Lesson Overview

Some data is naturally a table: a timetable with days and hours, a spreadsheet of sales by region and month, a seating plan with rows and seats. One row of lockers is not enough; you need a wall of lockers with rows and columns, where you name a locker with two numbers: "row 2, column 3".

Java has no special table type. A two-dimensional array is simply an array whose elements are themselves arrays: an outer row of lockers, each locker holding a whole row of lockers. This tiny idea explains every rule of this lesson, including the odd ones.

Why this matters: backend programs process tabular data all the time, such as query results, CSV files, and reports. The array-of-arrays shape, and the nested loops that walk it, are the base for all of it.

This lesson builds on arrays and loops (Lessons 1-2) and nested loops (BEG-013-L05).

Learning Objectives

By the end of this lesson you will be able to:

  • Create a two-dimensional array with new and with nested initializer lists.
  • Read and write a cell with grid[row][col].
  • Explain what grid.length and grid[r].length mean.
  • Visit every cell with nested loops and compute row, column, and overall totals.
  • Describe a two-dimensional array as an array of arrays, and explain what a row that is null means.
  • Avoid mixing up rows and columns when choosing loop bounds.

Written Explanation

An array of arrays

java
int[][] grid = new int[3][4];

Read int[][] as "array of arrays of int". This creates 3 rows, each with 4 columns: 12 cells, all starting at 0. The Java Language Specification describes multi-dimensional arrays in just this way: the elements of the outer array are references to sub-arrays.

text
  grid ---> [ row0 ] ---> [ 0 | 0 | 0 | 0 ]
            [ row1 ] ---> [ 0 | 0 | 0 | 0 ]
            [ row2 ] ---> [ 0 | 0 | 0 | 0 ]

You can also write the values out. Each inner pair of braces is one row:

java
int[][] sales = {
    {120, 80, 95},
    {60, 110, 70}
};   // 2 rows, 3 columns

Indexing: row first, then column

grid[r][c] means "go to row r, then to column c in that row". The row comes first. Both indices start at 0, and each follows the rule of Lesson 1: valid rows are 0 to grid.length - 1, valid columns are 0 to grid[r].length - 1.

Two sizes to remember:

  • grid.length is the number of rows (the length of the outer array).
  • grid[r].length is the number of columns in row r (the length of one inner array).

For new int[3][4], grid.length is 3 and grid[0].length is 4.

Visiting every cell: nested loops

Use one loop for the rows and one inside it for the columns:

java
for (int r = 0; r < grid.length; r++) {
    for (int c = 0; c < grid[r].length; c++) {
        // grid[r][c] is the current cell
    }
}

Using grid[r].length (not a fixed number) for the inner bound keeps the code correct when the table size changes. When you only read the values, the enhanced for works twice, once per level:

java
for (int[] row : grid) {
    for (int v : row) {
        // v is one value
    }
}

The outer loop's variable has type int[], one whole row, which is exactly what the outer array contains.

Row totals, column totals, diagonals

  • A row total adds grid[r][c] for all c with r fixed. The sum variable is reset at the start of each row, or each total goes into its own array element.
  • A column total fixes c and loops over r: grid[r][c] for all r.
  • For a square grid, the main diagonal is grid[i][i]: a single loop is enough.

Rows can be created separately

Because rows are independent arrays, new int[3][] creates only the outer array with three empty row slots, each null for now. You then create each row yourself, and the rows can even have different lengths (a ragged array):

java
int[][] tri = new int[4][];
tri[2] = new int[3];     // row 2 now exists with three columns

Using a row that has not been created is a NullPointerException, not an index error, because the row slot holds null. Most of the time you will use the new int[r][c] form, where all rows are created for you.

Examples

Example 1: build and print a table (simple)

java
public class Ex1 {
    public static void main(String[] args) {
        int[][] grid = new int[3][4];
        for (int r = 0; r < grid.length; r++) {
            for (int c = 0; c < grid[r].length; c++) {
                grid[r][c] = r * c;
            }
        }
        System.out.println(grid.length);
        System.out.println(grid[0].length);
        System.out.println(grid[2][3]);
        for (int[] row : grid) {
            for (int v : row) {
                System.out.print(v + " ");
            }
            System.out.println();
        }
    }
}

Output (compiled and run with JDK 21):

text
3
4
6
0 0 0 0 
0 1 2 3 
0 2 4 6 

grid[2][3] is row 2, column 3: 2 * 3 = 6. The inner loop prints one row; println() with no argument ends the line.

Example 2: monthly sales by shop (practical)

Rows are shops, columns are months.

java
public class Ex2 {
    public static void main(String[] args) {
        int[][] sales = {
            {120, 80, 95},
            {60, 110, 70}
        };
        int total = 0;
        int[] rowTotals = new int[sales.length];
        for (int r = 0; r < sales.length; r++) {
            for (int c = 0; c < sales[r].length; c++) {
                rowTotals[r] += sales[r][c];
                total += sales[r][c];
            }
        }
        System.out.println("Row 0: " + rowTotals[0] + ", row 1: " + rowTotals[1]);
        System.out.println("All: " + total);
        int col1 = 0;
        for (int r = 0; r < sales.length; r++) {
            col1 += sales[r][1];
        }
        System.out.println("Column 1: " + col1);
    }
}

Output:

text
Row 0: 295, row 1: 240
All: 535
Column 1: 190

One nested loop produced both the row totals (stored in a one-dimensional array with one slot per row) and the grand total. The column total needs only one loop: the column is fixed at 1 and the row varies.

Example 3: a ragged table and a missing row (realistic)

java
public class Ex3 {
    public static void main(String[] args) {
        int[][] tri = new int[4][];
        for (int r = 0; r < tri.length; r++) {
            tri[r] = new int[r + 1];
            for (int c = 0; c < tri[r].length; c++) {
                tri[r][c] = c + 1;
            }
        }
        for (int[] row : tri) {
            for (int v : row) {
                System.out.print(v + " ");
            }
            System.out.println();
        }
        int[][] pending = new int[2][];
        System.out.println(pending[0]);
        System.out.println(pending[0].length);
    }
}

Output (compiled with javac -g so the message shows variable names):

text
1 
1 2 
1 2 3 
1 2 3 4 
null
Exception in thread "main" java.lang.NullPointerException: Cannot read the array length because "pending[0]" is null
	at Ex3.main(Ex3.java:18)

Each row was created with its own length, so row r has r + 1 columns and the inner bound tri[r].length handles it automatically. In pending, the rows were never created, so pending[0] is null, and asking for its length fails with a NullPointerException rather than an index error.

Backend Insight

A database result, a CSV file, and a spreadsheet are all rows and columns, and a two-dimensional array is the most literal way to hold one in memory. In practice, backend code usually maps each row to an object with named fields rather than a bare [row][col] number, because data[3][2] says nothing about what column 2 means. Keep that in mind: tables of numbers fit grids well (scores, image pixels, game boards, matrices), while mixed data such as a name, an age, and an email belongs in objects, which you will build in the object-oriented modules.

One more connection: because a 2D array is an array of arrays, copying it with a simple copy duplicates only the outer row of pointers, so two copies would still share the rows. The next lessons and the exercises return to this.

Common Mistakes

Mistake 1: Swapping row and column

grid[3][2] on a 3-row, 4-column grid asks for row 3, which does not exist. Row first, column second, and the row bound is grid.length.

Mistake 2: Using the wrong length for a bound

  • Incorrect: inner loop c < grid.length (that is the row count)
  • Correct: inner loop c < grid[r].length

On a non-square grid the first version either skips cells or crashes.

Mistake 3: Not resetting the row total

If the row-total variable is declared outside the outer loop, it keeps adding across rows. Declare it inside the outer loop (or use one array slot per row).

Mistake 4: Using a row that was never created

After new int[2][], grid[0][0] throws a NullPointerException. Create the row first with grid[0] = new int[3];.

Mistake 5: Assuming grid.length is the total number of cells

It is only the number of rows. The total is rows times columns for a regular grid, or the sum of the row lengths for a ragged one.

Exercises

Try each exercise yourself before opening the solutions.

Level A: Foundation

A1. Write one statement that creates a grid of 2 rows and 3 columns of int, and one that creates the identity grid with rows {1, 0} and {0, 1}.

أظهر الحل A1

A1. int[][] g = new int[2][3]; and int[][] id = {{1, 0}, {0, 1}};.

A2. For int[][] g = new int[5][2];, what are g.length, g[0].length, and the number of cells? What is the largest valid g[r][c]?

أظهر الحل A2

A2. g.length is 5, g[0].length is 2, there are 10 cells, and the largest valid cell is g[4][1].

A3. Which cell does m[1][2] name if m is a table of seats where rows are rows of the cinema and columns are seats?

أظهر الحل A3

A3. Row 1, column 2: the second row of the cinema (counting from 0) and the third seat in it.

Level B: Application

B1. For a square int[][] m write a loop that sums the main diagonal.

أظهر الحل B1

B1.

java
int sum = 0;
for (int i = 0; i < m.length; i++) {
    sum += m[i][i];
}

B2. Write a method static int columnSum(int[][] m, int c) that returns the sum of column c.

أظهر الحل B2

B2.

java
static int columnSum(int[][] m, int c) {
    int sum = 0;
    for (int r = 0; r < m.length; r++) {
        sum += m[r][c];
    }
    return sum;
}

It assumes every row has a column c; for a ragged table, add a check on m[r].length.

B3. Print m as text, one row per line, with the values of a row separated by a space and no trailing space.

أظهر الحل B3

B3.

java
for (int r = 0; r < m.length; r++) {
    for (int c = 0; c < m[r].length; c++) {
        if (c > 0) {
            System.out.print(" ");
        }
        System.out.print(m[r][c]);
    }
    System.out.println();
}

Printing the separator before every value except the first avoids a trailing space.

Level C: Challenge

C1. Write a method that returns the transpose of a 2-row, 3-column int[][] (the result has 3 rows and 2 columns, and result[c][r] = m[r][c]).

أظهر الحل C1

C1.

java
static int[][] transpose(int[][] m) {
    int[][] t = new int[3][2];
    for (int r = 0; r < 2; r++) {
        for (int c = 0; c < 3; c++) {
            t[c][r] = m[r][c];
        }
    }
    return t;
}

Note the new array has the dimensions swapped. For other sizes, use m.length and m[0].length instead of 2 and 3.

C2. A cinema is a boolean[][] taken (true means the seat is taken). Write static int freeSeats(boolean[][] taken) that counts the free seats even if rows have different lengths, and explain why taken.length * taken[0].length is not safe.

أظهر الحل C2

C2.

java
static int freeSeats(boolean[][] taken) {
    int free = 0;
    for (boolean[] row : taken) {
        for (boolean seat : row) {
            if (!seat) {
                free++;
            }
        }
    }
    return free;
}

taken.length * taken[0].length assumes every row is as long as row 0 and that row 0 exists. With ragged rows it gives a wrong count, and with an empty taken it fails on taken[0].

Mini Challenge

Make a weekly timetable as String[][] with 5 rows (days) and 4 columns (periods). Fill in a few subjects and leave the other cells as null. Write a program that prints each day on one line, writes free for every empty cell, and finally prints how many free periods there are in total and the day with the most free periods.

Check your work: the outer bound is length, the inner bound is [r].length, and you tested for null before treating a cell as text.

Summary & Next Lesson

Key takeaways

  • A two-dimensional array is an array of arrays: new int[rows][cols].
  • grid[r][c] is row first, then column. grid.length counts rows and grid[r].length counts the columns of row r.
  • Nested loops visit every cell; use grid[r].length as the inner bound.
  • Row totals fix the row, column totals fix the column, and a square grid's diagonal is grid[i][i].
  • Rows can be created separately or be different lengths; a row that was never created is null.

Next lesson

The next lesson, "Working with Strings", turns from numbers in lockers to text: the String methods you use every day to measure, cut, search, and clean text.

Self-check

  1. In grid[2][3], which number is the row?
  2. What is grid[0].length for new int[3][4]?
  3. Why use grid[r].length for the inner loop bound?
  4. How do you sum column c of a grid?
  5. What goes wrong with new int[2][] and then grid[0][0] = 1;?
أظهر الإجابات
  1. The first one: 2 is the row.
  2. 4.
  3. It adapts to the actual row length, so the code keeps working when the table changes size or the rows differ in length.
  4. Loop r over all rows and add grid[r][c].
  5. Row 0 was never created, so it is null and the assignment throws a NullPointerException.

References

  • Oracle, Java Language Specification SE 21, chapter 10: Arrays. It supports: an array's component type can itself be an array type, so multi-dimensional arrays are arrays whose elements refer to sub-arrays, and nested initializers create them.
  • The outputs and the NullPointerException message were produced by compiling and running the code with JDK 21. The wall-of-lockers picture is this course's own teaching device.

التطبيق

طبّق هذا الدرس بمختبر الوحدة وتمارينها واختبارها.

المختبر والتمارين الاختبار

تعليم الدرس كمكتمل اختياري ويمكنك التراجع عنه في أي وقت. يبقى تقدمك محفوظًا في هذا المتصفح.

اكتب للبحث في المستويات والمراحل والوحدات والدروس.