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
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
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ớ:
- Chỉ số chạy từ 0 tới
length - 1. - Kích thước mảng cố định sau khi tạo. Cần danh sách co giãn →
ArrayList(bài 9). - Phần tử chưa gán có giá trị mặc định:
0cho số,falsechoboolean,nullcho kiểu tham chiếu.
Truy cập ngoài phạm vi gây lỗi lúc chạy:
Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: Index 5 out of bounds for length 5Lỗ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
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 —dchỉ 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
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
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
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ờ:
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:
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
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ệc | Phương thức |
|---|---|
| Độ dài, ký tự thứ i | length(), charAt(i) |
| Tìm | indexOf, lastIndexOf, contains, startsWith, endsWith |
| Cắt | substring(bắt đầu, kết thúc) — không gồm vị trí kết thúc |
| Đổi | toUpperCase, toLowerCase, trim/strip, replace |
| Tách / nối | split(regex), String.join(dấu nối, ...) |
| So sánh | equals, equalsIgnoreCase, compareTo (thứ tự từ điển) |
| Kiểm tra rỗng | isEmpty() (độ dài 0), isBlank() (chỉ khoảng trắng) |
equals và ==
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
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 |
|---|---|
%s | Chuỗi (hoặc bất kỳ đối tượng) |
%d | Số nguyên; %,d có dấu phân cách nghìn |
%.2f | Số thực 2 chữ số thập phân |
%10s, %-10s | Căn phải / căn trái trong 10 ô |
%n | Xuống dòng |
Duyệt ký tự
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:
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
- 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.
- Viết chương trình kiểm tra mảng đã sắp xếp tăng dần chưa.
- Chuẩn hoá họ tên:
" nGUYỄN vĂn aN "→"Nguyễn Văn An"(gợi ý:trim,split("\\s+"),substring,toUpperCase). - Đếm số từ trong một câu và tìm từ dài nhất.
- 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. forcó chỉ số khi cần vị trí/sửa phần tử,for-eachkhi chỉ đọc;Arrays.toString,sort,copyOf,equals.- Mảng là kiểu tham chiếu:
b = akhông sao chép, dùngclone()/copyOf. - String bất biến; so sánh bằng
equals;substring(a, b)không gồmb; text block vàformattedcho 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.
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.