Bỏ qua điều hướng
EN
Tất cả bài viết

Java cơ bản #4: mảng, mảng hai chiều và xử lý chuỗi String, StringBuilder

Lưu nhiều giá trị với mảng: khai báo, duyệt bằng for và for-each, Arrays.sort, tìm kiếm, mảng hai chiều; làm chủ String: bất biến, các phương thức hay dùng, equals vs ==, text block, String.format, StringBuilder khi nối chuỗi trong vòng lặp.

13 phút đọc

Ôn tập bài nàyThẻ ghi nhớ & câu hỏi ôn tập7 thẻ ghi nhớ · 6 câu trắc nghiệm
Mục lục

Một lớp có 40 sinh viên — không ai khai báo 40 biến diem1, diem2… Ta cần một cấu trúc chứa nhiều giá trị cùng kiểu: đó là mảng. Và vì phần lớn dữ liệu thực tế là chữ — tên, email, địa chỉ, nội dung tin nhắn — nửa sau bài này dành cho String, lớp bạn sẽ dùng nhiều nhất trong Java.

Mảng

Khai báo và truy cập

MangCoBan.java
void main() {
    int[] diem = {8, 6, 9, 7, 10};          // khai báo kèm giá trị
    double[] luong = new double[3];          // 3 phần tử, mặc định 0.0
    String[] ten = new String[2];            // mặc định null
 
    IO.println(diem[0]);                     // 8 — chỉ số bắt đầu từ 0
    IO.println(diem[diem.length - 1]);       // 10 — phần tử cuối
    diem[1] = 7;                              // gán lại
    luong[0] = 15_000_000;
    IO.println("Số phần tử: " + diem.length + ", tên đầu: " + ten[0]);
}

Ba điều cần nhớ:

  1. Chỉ số chạy từ 0 tới length - 1.
  2. Kích thước mảng cố định sau khi tạo. Cần danh sách co giãn → ArrayList (bài 9).
  3. Phần tử chưa gán có giá trị mặc định: 0 cho số, false cho boolean, null cho kiểu tham chiếu.

Truy cập ngoài phạm vi gây lỗi lúc chạy:

loi-chi-so
Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: Index 5 out of bounds for length 5

Lỗi "lệch một" (off-by-one) — viết i <= diem.length thay vì i < diem.length — là nguyên nhân hàng đầu của lỗi này.

Duyệt mảng

DuyetMang.java
void main() {
    int[] diem = {8, 6, 9, 7, 10};
 
    // for với chỉ số: khi cần biết vị trí hoặc sửa phần tử
    for (int i = 0; i < diem.length; i++) {
        IO.println("Sinh viên " + (i + 1) + ": " + diem[i]);
    }
 
    // for-each: khi chỉ cần đọc từng giá trị — gọn, không lo lệch chỉ số
    int tong = 0, max = diem[0];
    for (int d : diem) {
        tong += d;
        if (d > max) max = d;
    }
    IO.println("Trung bình: " + (double) tong / diem.length + ", cao nhất: " + max);
}

Lưu ý: gán vào biến của for-each (d = 0) không sửa mảng — d chỉ là bản sao giá trị. Muốn sửa phần tử, dùng for có chỉ số.

Lớp tiện ích Arrays

TienIchArrays.java
import java.util.Arrays;
 
void main() {
    int[] so = {42, 7, 19, 3, 25};
    IO.println(Arrays.toString(so));           // [42, 7, 19, 3, 25] — in mảng trực tiếp chỉ ra địa chỉ kiểu [I@1b6d3586
 
    Arrays.sort(so);                           // sắp xếp tăng dần, sửa trực tiếp mảng
    IO.println(Arrays.toString(so));           // [3, 7, 19, 25, 42]
    IO.println(Arrays.binarySearch(so, 19));   // 2 — tìm nhị phân, mảng PHẢI đã sắp xếp
 
    int[] banSao = Arrays.copyOf(so, 7);       // sao chép, phần thêm là 0
    int[] doan = Arrays.copyOfRange(so, 1, 3); // [7, 19] — từ 1 tới trước 3
    int[] dayMot = new int[4];
    Arrays.fill(dayMot, 1);
    IO.println(Arrays.toString(banSao) + " " + Arrays.toString(doan) + " " + Arrays.toString(dayMot));
    IO.println(Arrays.equals(so, banSao));     // false — so sánh nội dung (khác độ dài)
}

Mảng là kiểu tham chiếu

ThamChieuMang.java
import java.util.Arrays;
 
void main() {
    int[] a = {1, 2, 3};
    int[] b = a;            // b trỏ CÙNG mảng với a, không phải bản sao
    b[0] = 99;
    IO.println(Arrays.toString(a));   // [99, 2, 3] — a cũng đổi!
 
    int[] c = a.clone();    // bản sao thật sự
    c[1] = 0;
    IO.println(Arrays.toString(a) + " " + Arrays.toString(c));
}

Đây là ví dụ đầu tiên về sự khác biệt giữa kiểu nguyên thuỷ và kiểu tham chiếu nhắc ở bài 2 — một khái niệm sẽ theo bạn suốt quá trình học Java.

Thuật toán nhỏ: đảo ngược và đếm

ThuatToanMang.java
import java.util.Arrays;
 
void main() {
    int[] so = {1, 2, 3, 4, 5, 6};
    // Đảo ngược tại chỗ: đổi chỗ đầu–cuối, tiến dần vào giữa
    for (int i = 0, j = so.length - 1; i < j; i++, j--) {
        int tam = so[i];
        so[i] = so[j];
        so[j] = tam;
    }
    IO.println(Arrays.toString(so));   // [6, 5, 4, 3, 2, 1]
 
    // Đếm tần suất điểm 0–10
    int[] cacDiem = {7, 8, 7, 10, 5, 8, 7};
    int[] dem = new int[11];
    for (int d : cacDiem) dem[d]++;
    for (int d = 0; d <= 10; d++) {
        if (dem[d] > 0) IO.println("Điểm " + d + ": " + "*".repeat(dem[d]));
    }
}

Mảng hai chiều

Mảng hai chiều là mảng của các mảng — hợp cho bảng, ma trận, bàn cờ:

MaTran.java
void main() {
    int[][] bang = {
        {8, 7, 9},     // điểm sinh viên 1: Toán, Lý, Hoá
        {6, 9, 8},
        {10, 8, 7},
    };
    IO.println("Số dòng: " + bang.length + ", số cột: " + bang[0].length);
    IO.println("SV 2 môn Lý: " + bang[1][1]);   // 9
 
    for (int i = 0; i < bang.length; i++) {
        int tong = 0;
        for (int j = 0; j < bang[i].length; j++) {
            tong += bang[i][j];
        }
        IO.println("SV " + (i + 1) + " trung bình " + String.format("%.2f", tong / 3.0));
    }
 
    char[][] banCo = new char[3][3];
    for (char[] hang : banCo) java.util.Arrays.fill(hang, '.');
    banCo[1][1] = 'X';
    for (char[] hang : banCo) IO.println(new String(hang));
}

String

Chuỗi là bất biến (immutable)

Mọi phương thức "sửa" chuỗi thực ra tạo chuỗi mới; chuỗi cũ không đổi:

BatBien.java
void main() {
    String s = "java";
    s.toUpperCase();           // tạo "JAVA" rồi bỏ đi — s không đổi
    IO.println(s);             // java
    s = s.toUpperCase();       // gán lại mới có tác dụng
    IO.println(s);             // JAVA
}

Bất biến giúp chuỗi an toàn khi dùng chung giữa nhiều nơi và nhiều luồng, và cho phép Java tái sử dụng các chuỗi giống nhau (string pool).

Các phương thức hay dùng

PhuongThucChuoi.java
void main() {
    String email = "  Nguyen.Van.An@Gmail.com  ";
    String e = email.trim().toLowerCase();         // bỏ khoảng trắng 2 đầu, chữ thường
    IO.println(e);                                  // nguyen.van.an@gmail.com
    IO.println(e.length());                         // 23
    IO.println(e.charAt(0));                        // n
    IO.println(e.indexOf('@'));                     // 13 (-1 nếu không có)
    IO.println(e.substring(0, e.indexOf('@')));     // nguyen.van.an — từ 0 tới TRƯỚC 13
    IO.println(e.substring(e.indexOf('@') + 1));    // gmail.com
    IO.println(e.contains("gmail") + " " + e.endsWith(".com") + " " + e.startsWith("ng"));
    IO.println(e.replace(".", "_"));                // thay mọi "."
    IO.println(String.join(" | ", e.split("[.@]")));   // tách theo regex rồi nối lại
    IO.println("   ".isBlank() + " " + "".isEmpty());
    IO.println("ha".repeat(3));                     // hahaha
}
ViệcPhương thức
Độ dài, ký tự thứ ilength(), charAt(i)
TìmindexOf, lastIndexOf, contains, startsWith, endsWith
Cắtsubstring(bắt đầu, kết thúc) — không gồm vị trí kết thúc
ĐổitoUpperCase, toLowerCase, trim/strip, replace
Tách / nốisplit(regex), String.join(dấu nối, ...)
So sánhequals, equalsIgnoreCase, compareTo (thứ tự từ điển)
Kiểm tra rỗngisEmpty() (độ dài 0), isBlank() (chỉ khoảng trắng)

equals và ==

EqualsChuoi.java
void main() {
    String a = "xin chào";
    String b = "xin chào";              // literal giống nhau → dùng chung một đối tượng trong string pool
    String c = new String("xin chào");  // đối tượng mới
    String d = "xin " + "chào".trim();  // chuỗi tạo lúc chạy cũng là đối tượng mới
 
    IO.println(a == b);        // true  — tình cờ cùng đối tượng (đừng dựa vào điều này!)
    IO.println(a == c);        // false
    IO.println(a == d);        // false
    IO.println(a.equals(c) && a.equals(d));   // true
 
    String nhap = null;
    IO.println("admin".equals(nhap));   // false — đặt literal bên trái tránh NullPointerException
    // nhap.equals("admin") → NullPointerException
}

Định dạng chuỗi

DinhDang.java
void main() {
    String ten = "Lan";
    double diem = 8.4567;
    int tien = 1_250_000;
    IO.println(String.format("%s đạt %.2f điểm, học bổng %,d đồng", ten, diem, tien));
    IO.println("%-10s|%5d|".formatted("Cột trái", 42));   // căn trái 10 ô, căn phải 5 ô
 
    // Text block (Java 15+): chuỗi nhiều dòng, giữ nguyên xuống dòng, không cần \n
    String json = """
        {
          "ten": "%s",
          "diem": %.1f
        }
        """.formatted(ten, diem);
    IO.println(json);
}
Định dạngÝ nghĩa
%sChuỗi (hoặc bất kỳ đối tượng)
%dSố nguyên; %,d có dấu phân cách nghìn
%.2fSố thực 2 chữ số thập phân
%10s, %-10sCăn phải / căn trái trong 10 ô
%nXuống dòng

Duyệt ký tự

DemNguyenAm.java
void main() {
    String cau = "Lap trinh Java that thu vi";
    int nguyenAm = 0;
    for (char c : cau.toLowerCase().toCharArray()) {
        if ("aeiou".indexOf(c) >= 0) nguyenAm++;
    }
    IO.println("Số nguyên âm: " + nguyenAm);
 
    // Kiểm tra chuỗi đối xứng (palindrome), bỏ qua khoảng trắng và hoa thường
    String s = "Never odd or even".replace(" ", "").toLowerCase();
    boolean doiXung = new StringBuilder(s).reverse().toString().equals(s);
    IO.println("Đối xứng: " + doiXung);
    IO.println(Character.isDigit('7') + " " + Character.isLetter('đ') + " " + Character.toUpperCase('a'));
}

StringBuilder: nối chuỗi trong vòng lặp

Vì String bất biến, mỗi lần s += x trong vòng lặp tạo một chuỗi mới và sao chép toàn bộ nội dung cũ — với hàng chục nghìn lần lặp, chương trình chậm rõ rệt. StringBuilder là chuỗi có thể sửa:

NoiChuoi.java
void main() {
    int n = 50_000;
 
    long t = System.nanoTime();
    String s = "";
    for (int i = 0; i < n; i++) s += i % 10;
    IO.println("String +=     : " + (System.nanoTime() - t) / 1_000_000 + " ms");
 
    t = System.nanoTime();
    StringBuilder sb = new StringBuilder();
    for (int i = 0; i < n; i++) sb.append(i % 10);
    String ketQua = sb.toString();
    IO.println("StringBuilder : " + (System.nanoTime() - t) / 1_000_000 + " ms");
    IO.println(s.equals(ketQua));
 
    StringBuilder b = new StringBuilder("Java");
    b.append(" 25").insert(0, "Học ").reverse();
    IO.println(b);
}

Quy tắc: nối vài chuỗi trong một biểu thức thì cứ dùng + (trình biên dịch tự tối ưu); nối trong vòng lặp thì dùng StringBuilder.

Bài tập

  1. Nhập 5 số vào mảng, in số lớn nhất, nhỏ nhất, trung bình và các số lớn hơn trung bình.
  2. Viết chương trình kiểm tra mảng đã sắp xếp tăng dần chưa.
  3. Chuẩn hoá họ tên: " nGUYỄN vĂn aN " → "Nguyễn Văn An" (gợi ý: trim, split("\\s+"), substring, toUpperCase).
  4. Đếm số từ trong một câu và tìm từ dài nhất.
  5. Cộng hai ma trận 3×3 và in kết quả dạng bảng.

Tổng kết

  • Mảng: kích thước cố định, chỉ số từ 0 tới length - 1; cẩn thận lỗi lệch một.
  • for có chỉ số khi cần vị trí/sửa phần tử, for-each khi chỉ đọc; Arrays.toString, sort, copyOf, equals.
  • Mảng là kiểu tham chiếu: b = a không sao chép, dùng clone()/copyOf.
  • String bất biến; so sánh bằng equals; substring(a, b) không gồm b; text block và formatted cho chuỗi dài.
  • Nối chuỗi trong vòng lặp dùng StringBuilder.
  • Bài tiếp theo: tách chương trình thành các phương thức để tái sử dụng và dễ đọc.

Nguồn tham khảo: dev.java — "Arrays", "Strings and StringBuilder"; tài liệu API Java 25 — java.lang.String, java.util.Arrays; JEP 378: Text Blocks; Java Programming MOOC — University of Helsinki.

Đang tải câu hỏi luyện tập…

Bài này thế nào?

Bài viết có hữu ích với bạn?

Đánh giá giúp mình biết nên viết thêm về chủ đề nào.

Đang tải đánh giá…
Chọn số sao từ 1 đến 5

Bình luận

Đăng nhập để bình luận không cần nhập tên và được duyệt nhanh hơn.

0/2000
Đang tải bình luận…

Đọc tiếp

Bài số № 55

Java cơ bản #12: dự án cuối — ứng dụng quản lý chi tiêu với Maven và JUnit

Gom toàn bộ kiến thức vào một dự án thật: cấu trúc Maven và pom.xml, chia lớp model–kho dữ liệu–service–giao diện, lưu file CSV, thống kê bằng Stream, cảnh báo ngân sách, viết kiểm thử JUnit 5, đóng gói file jar chạy được — và lộ trình học tiếp sau Java cơ bản.

19 phút đọc

Bài số № 54

Java cơ bản #11: lambda, tham chiếu phương thức, Stream API và Optional

Lập trình kiểu hàm trong Java: lambda và functional interface (Predicate, Function, Consumer, Supplier), tham chiếu phương thức ::, Stream với filter–map–reduce, sorted, collect, groupingBy, Optional thay cho null — cùng lời khuyên khi nào vòng lặp vẫn tốt hơn stream.

14 phút đọc

Bài số № 53

Java cơ bản #10: xử lý ngoại lệ và đọc ghi file với java.nio.file

Khi chương trình gặp lỗi: đọc stack trace, try/catch/finally, checked và unchecked exception, throw và throws, tự định nghĩa ngoại lệ nghiệp vụ, try-with-resources tự đóng tài nguyên; đọc ghi file văn bản và CSV bằng Files, Path, UTF-8 — kèm các thói quen xử lý lỗi nên tránh.

15 phút đọc