Backend

99 Ngày Java — Ngày 08: Mảng (Arrays)

SSite Admin
5 tháng 08, 2026 7 phút đọc 126 lượt xem
99 Ngày Java — Ngày 08: Mảng (Arrays)

Ngày 07 khép lại với lời hẹn: gặp mảng, off-by-one sẽ hết chỗ trốn. Hôm nay trả lời hẹn — mảng (array) là cấu trúc dữ liệu đầu tiên của hành trình: một dãy ô cùng kiểu, độ dài cố định, đánh số từ 0. Ta sẽ khai báo, duyệt bằng for/for-each, leo lên mảng 2 chiều, điểm qua hộp đồ nghề java.util.Arrays — và dính một cái bẫy mới toanh: gán mảng không phải là copy.

Sketchnote Ngày 08: mảng (arrays) trong Java

Mảng là gì? Khai báo & khởi tạo

Tưởng tượng một dãy tủ khóa đánh số liên tiếp: mỗi ô chứa một giá trị cùng kiểu, và muốn lấy gì chỉ cần biết số thứ tự của ô. Đó chính xác là mảng:

// Khai báo + khởi tạoint[] diem = new int[5];          // 5 phần tử — mặc định toàn 0
diem[0] = 9;                      // chỉ số bắt đầu từ 0
diem[4] = 7;                      // phần tử CUỐI = length - 1

String[] mon = {"Toán", "Lý", "Hóa"};    // khởi tạo kèm giá trị luôn
System.out.println(mon.length);          // 3 — length là FIELD, không có ()

// Giá trị mặc định: số → 0, boolean → false, đối tượng (String…) → null
  • Chỉ số chạy từ 0 đến length - 1 — mảng 5 phần tử không có ô số 5. Đây là nguồn gốc của mọi lỗi off-by-one.

  • new int[5] cấp phát trên heap và điền sẵn giá trị mặc định (Ngày 03): số 0, boolean false, đối tượng null.

  • Độ dài chốt lúc tạo, không đổi được nữa — length là field final, không phải method như của String.

Duyệt mảng: for & for-each

Mảng và vòng lặp là cặp bài trùng — Ngày 07 học lặp chính là để hôm nay duyệt:

int[] diem = {9, 7, 8, 10, 6};

// for kinh điển — có chỉ số i, sửa được phần tửfor (int i = 0; i < diem.length; i++) {   // i < length, KHÔNG phải <=
    diem[i] += 1;                          // cộng 1 điểm cho cả lớp
}

// for-each — gọn nhất khi CHỈ ĐỌC từng phần tử (hẹn từ Ngày 07!)for (int d : diem) {
    System.out.println(d);
}

diem[5] = 10;  // 💥 ArrayIndexOutOfBoundsException:
               //    Index 5 out of bounds for length 5

Chọn kiểu nào? Cần chỉ số hoặc cần sửa phần tửfor kinh điển; chỉ đọc từng phần tử → for-each cho gọn và không thể lệch chỉ số. Còn dòng cuối là ArrayIndexOutOfBoundsException — runtime ném ngay khi chạm ô không tồn tại. Java kiểm tra biên mỗi lần truy cập, nên lỗi off-by-one của Ngày 07 không âm thầm phá bộ nhớ như C, mà nổ to, rõ, kèm chỉ số phạm lỗi — đọc message là biết sửa đâu.

Mảng 2 chiều

Java không có mảng 2 chiều "thật" — chỉ có mảng của mảng: mỗi phần tử của mảng ngoài lại là một mảng con. Vì thế vòng lặp lồng nhau của Ngày 07 khớp vào đây như in:

// Mảng 2 chiều = mảng CỦA mảng — bảng điểm, bàn cờ, ma trậnint[][] bang = new int[3][4];        // 3 hàng × 4 cột
bang[1][2] = 7;                      // hàng 1, cột 2

for (int i = 0; i < bang.length; i++) {          // bang.length     = số hàng
    for (int j = 0; j < bang[i].length; j++) {   // bang[i].length  = số cột
        System.out.print(bang[i][j] + " ");
    }
    System.out.println();            // hết một hàng thì xuống dòng
}

// Mảng "răng cưa" (jagged): mỗi hàng một độ dài riêngint[][] tamGiac = {
    {1},
    {1, 2},
    {1, 2, 3},
};

Nhờ "mảng của mảng" mà mỗi hàng có thể dài ngắn khác nhau — mảng răng cưa (jagged) như tamGiac ở trên là hoàn toàn hợp lệ. Hệ quả cần nhớ: bang[i].length phải hỏi từng hàng, đừng giả định mọi hàng dài bằng hàng đầu.

Lớp tiện ích Arrays

Tự viết vòng lặp để in, sắp xếp, sao chép mảng là bài tập tốt — nhưng JDK đã gói sẵn trong java.util.Arrays:

import java.util.Arrays;

int[] a = {5, 2, 9, 1, 7};

System.out.println(Arrays.toString(a));      // [5, 2, 9, 1, 7] — in tử tế
Arrays.sort(a);                              // [1, 2, 5, 7, 9] — sắp xếp TẠI CHỖint viTri = Arrays.binarySearch(a, 7);       // 3 — CHỈ đúng trên mảng ĐÃ sort!

int[] banSao = Arrays.copyOf(a, a.length);   // bản sao thật sự (không cùng tham chiếu)int[] dau3   = Arrays.copyOfRange(a, 0, 3);  // [1, 2, 5] — từ 0 tới TRƯỚC 3
Arrays.fill(a, 0);                           // lấp đầy toàn 0

int[][] m = {{1, 2}, {3, 4}};
System.out.println(Arrays.deepToString(m));  // [[1, 2], [3, 4]] — cho mảng 2 chiều
  • In mảng mà quên Arrays.toString sẽ nhận [I@1b6d3586 — tên kiểu + mã băm, không phải nội dung.

  • Arrays.sort sửa tại chỗ (mảng gốc đổi luôn); binarySearch chỉ đáng tin trên mảng đã sort — sai điều kiện này kết quả vô nghĩa.

  • copyOfRange(a, 0, 3) lấy từ chỉ số 0 đến trước 3 — nửa đóng nửa mở, đúng quy ước i < n quen thuộc.

Bẫy tham chiếu: gán mảng không phải copy

Mảng là đối tượng trên heap — biến mảng chỉ giữ tham chiếu (địa chỉ) trỏ tới nó. Dấu = vì thế chỉ chép địa chỉ, không chép dữ liệu:

int[] a = {1, 2, 3};
int[] b = a;                  // KHÔNG copy — b trỏ vào CÙNG mảng với a
b[0] = 99;
System.out.println(a[0]);     // 99! — sửa qua b, a "cũng đổi" (thực ra là MỘT)

int[] x = {1, 2, 3};
int[] y = {1, 2, 3};
System.out.println(x == y);              // false — so sánh THAM CHIẾU
System.out.println(Arrays.equals(x, y)); // true  — so sánh NỘI DUNG

// Muốn bản sao độc lập: Arrays.copyOf / copyOfRange / x.clone()// Mảng có độ dài CỐ ĐỊNH — "mảng co giãn" tên là ArrayList, hẹn giai đoạn Collections

Đây là lần chạm mặt đầu tiên với ngữ nghĩa tham chiếu — khái niệm sẽ theo bạn suốt phần OOP (Ngày 11 trở đi) và là gốc của câu hỏi equals vs == ở Ngày 09. Quy tắc bỏ túi: với mảng, so sánh nội dung dùng Arrays.equals (2 chiều: deepEquals), sao chép dùng Arrays.copyOf — đừng bao giờ tin === làm việc đó.

Bài tập nhỏ

  • Nhập mảng điểm {9, 7, 8, 10, 6}: tính trung bình, tìm max — không dùng thư viện, chỉ vòng lặp Ngày 07.

  • Đảo ngược mảng tại chỗ (không tạo mảng mới) — gợi ý: hai "con trỏ" từ hai đầu tiến vào giữa.

  • Tạo bảng cửu chương 9×9 bằng mảng 2 chiều rồi in đẹp bằng vòng lặp lồng nhau — nối tiếp bài tập Ngày 07.

  • Chạy thử int[] b = a; rồi sửa b[0] — in a trước và sau bằng Arrays.toString để tận mắt thấy bẫy tham chiếu.

Kết luận

Cấu trúc dữ liệu đầu tiên đã nằm trong tay: mảng cùng kiểu, độ dài cố định, chỉ số từ 0, duyệt bằng for/for-each, đồ nghề Arrays, và bài học lớn — gán là chép tham chiếu, không phải dữ liệu. Ngày 09 ta gặp "mảng ký tự" nổi tiếng nhất Java: String — vì sao nó bất biến, và vì sao == với chuỗi lại là cái bẫy kinh điển. Hẹn gặp lại!

S

Site Admin

Engineer and writer. Building things with TypeScript and distributed systems.

Bình luận (0)

Bạn cần đăng nhập bằng Google để bình luận.

Hãy là người bình luận đầu tiên.

Bài viết liên quan

99 Ngày Java — Ngày 58: Optional

Optional là công cụ cho kiểu trả về — không field, không tham số, không bọc collection; chuỗi map/flatMap/filter thay kim tự tháp if; orElse luôn tính tham số còn orElseGet lười, orElseThrow cho “không có là lỗi” — và các anti-pattern isPresent + get, orElse(null).

23 thg 9, 20269 phút9
99 Ngày Spring — Ngày 58: Spring Boot Actuator

Boot 3 mặc định chỉ lộ health; bản đồ endpoint an toàn và nguy hiểm; HealthIndicator tự viết với liveness tách khỏi readiness cho Kubernetes, info từ build và git, metrics Micrometer với tag hữu hạn — và ba lớp khóa: cổng riêng, SecurityFilterChain với EndpointRequest, exclude env/heapdump/threaddump.

23 thg 9, 202610 phút5
So sánh EasyExcel và Apache Fesod: khác biệt thật nằm ở đâu?

Fesod là hậu duệ của EasyExcel, cùng engine SAX/SXSSF. Khác biệt kiểm chứng được: vòng đời dự án, POI 5.2.5 vs 5.5.1, các API mới (numRows, csv(), headerMergeStrategy) và độ bền với file xấu — không phải hiệu năng gấp nhiều lần.

23 thg 9, 20268 phút1