String là gì?
Một dòng if (status == "ACTIVE") tưởng vô hại đã khiến cả nghiệp vụ kiểm thử "trượt" qua hàng loạt tài khoản: điều kiện luôn trả về false, dù dữ liệu trên màn hình rõ ràng là "ACTIVE". Bug không nằm ở logic — nó nằm ở chỗ lập trình viên đã so sánh hai String bằng == thay vì .equals(). Với một tester, hiểu đúng String không phải chuyện "kiến thức cơ bản cho có" — đó là ranh giới giữa một test case đáng tin và một test case âm thầm cho kết quả sai.
String xuất hiện ở gần như mọi nơi trong công việc kiểm thử: từ dữ liệu nhập liệu, response của API, đến từng dòng log bạn so khớp. Thế nhưng đây cũng là kiểu dữ liệu dễ gây hiểu lầm nhất: nó immutable (không thể thay đổi), so sánh sai cách thì cho kết quả sai một cách thầm lặng, và xử lý ẩu thì ngốn bộ nhớ mà không ai để ý. Nắm chắc String giúp bạn viết được những kịch bản kiểm thử chính xác và không bị đánh lừa bởi những lỗi "trông thì đúng".
Trong bài viết này, bạn sẽ nắm được:
Stringtrong Java thực chất là gì và vì sao nó immutable.- Khác biệt cốt lõi giữa
==và.equals()— cái bẫy kinh điển khiến test sai.- Bộ phương thức xử lý chuỗi thiết yếu, kèm ví dụ dùng được ngay.
- Khi nào nên dùng
StringBuilderthay cho phép nối chuỗi thông thường.
String là gì?
String là kiểu dữ liệu dùng để biểu diễn chuỗi ký tự — hiểu đơn giản là những đoạn văn bản, câu chữ từ vài ký tự đến nguyên cả cuốn tiểu thuyết. Ngay khi bạn viết "Hello, World!" trong chương trình đầu tiên của mình, bạn đã dùng String rồi.
Tạo một String đơn giản như sau:
String greeting = "Hello, Java!";
Vì sao String là "immutable"?
Trong Java, String là immutable — tức là không thể thay đổi sau khi đã tạo. Điều này có nghĩa là mỗi lần bạn "chỉnh sửa" một chuỗi, Java âm thầm tạo ra một chuỗi mới và để lại chuỗi cũ trong bộ nhớ. Vậy nên nếu bạn cần thao tác nhiều với chuỗi (ví dụ nối chuỗi trong vòng lặp), hãy cẩn thận để bộ nhớ không biến thành… bãi chiến trường.
Kinh nghiệm thực chiến. Tính immutable chính là lý do bạn không bao giờ được so sánh nội dung hai chuỗi bằng
==. Toán tử==so sánh tham chiếu (hai biến có cùng trỏ về một đối tượng trong bộ nhớ không), chứ không so sánh nội dung ký tự. Đây là cái bẫy khiến vô số bug "lọt lưới" qua các bài kiểm thử.
So sánh chuỗi: == và .equals()
Đây là kiến thức bắt buộc phải nắm trước khi viết bất kỳ điều kiện kiểm tra nào liên quan đến chuỗi. equals kiểm tra chính xác từng ký tự của nội dung, còn equalsIgnoreCase thì thoải mái hơn — không phân biệt chữ hoa, chữ thường.
String name1 = "Alice";
String name2 = "alice";
boolean isEqual = name1.equals(name2); // Kết quả: false
boolean isEqualIgnoreCase = name1.equalsIgnoreCase(name2); // Kết quả: true
| Tiêu chí | == | .equals() |
|---|---|---|
| So sánh điều gì | Tham chiếu (cùng một đối tượng trong bộ nhớ?) | Nội dung (từng ký tự có giống nhau?) |
| Dùng để | Kiểm tra hai biến có trỏ về cùng một object | Kiểm tra hai chuỗi có cùng nội dung |
| Khi so sánh nội dung chuỗi | Không đáng tin — dễ trả về sai | Cách đúng cần dùng |
Quy tắc ghi nhớ: so sánh nội dung chuỗi thì luôn dùng .equals() (hoặc equalsIgnoreCase() nếu không quan tâm hoa/thường), tuyệt đối không dùng ==.
Các phương thức cơ bản làm việc với String
Dưới đây là bộ phương thức cực kỳ hữu ích khi bạn cần "biến hóa" với String trong Java. Mỗi phương thức kèm một ví dụ dùng được ngay.
length() — đếm độ dài chuỗi
Giống như đếm số ký tự trong câu, length() cho bạn biết độ dài của chuỗi.
String message = "Hello, Java!";
int length = message.length(); // Kết quả: 11
"Hello, Java!" có 11 ký tự — đếm cả dấu cách và dấu chấm than.
charAt(int index) — lấy ký tự tại một vị trí
Hàm charAt giúp bạn lấy ra từng ký tự trong chuỗi. Cần ký tự ở vị trí nào, cứ gọi charAt(index).
String name = "Alice";
char firstChar = name.charAt(0); // Kết quả: 'A'
Lưu ý quan trọng. Vị trí trong chuỗi bắt đầu từ 0, không phải từ 1. Vì vậy ký tự "A" của
"Alice"nằm ởcharAt(0). Đây là nguồn gốc của rất nhiều lỗi lệch một (off-by-one) — đáng nhắc lại nhiều lần.
substring(int beginIndex, int endIndex) — cắt một đoạn chuỗi
Muốn cắt một đoạn từ chuỗi? substring lấy phần từ beginIndex đến endIndex — nhưng không tính ký tự ở endIndex. Vị trí vẫn bắt đầu từ 0.
String text = "Hello, world!";
String sub = text.substring(0, 5); // Kết quả: "Hello"
contains(CharSequence s) — kiểm tra có chứa chuỗi con
Muốn biết chuỗi có "chứa" đoạn nào không? contains kiểm tra xem chuỗi con bạn cần tìm có xuất hiện hay không.
String text = "Hello, world!";
boolean hasWorld = text.contains("world"); // Kết quả: true
toLowerCase() và toUpperCase() — đổi hoa/thường
Biến chuỗi thành chữ thường hoặc chữ hoa — dùng khi bạn muốn đồng nhất cách viết trước khi so sánh.
String name = "Alice";
String lowerName = name.toLowerCase(); // Kết quả: "alice"
String upperName = name.toUpperCase(); // Kết quả: "ALICE"
trim() — dọn khoảng trắng thừa
Chuỗi của bạn có bị "dính bụi" với những dấu cách thừa ở đầu/đuôi không? trim sẽ dọn dẹp sạch sẽ.
String messyText = " Hello ";
String cleanText = messyText.trim(); // Kết quả: "Hello"
replace(CharSequence target, CharSequence replacement) — thay thế chuỗi con
Khi bạn muốn thay một đoạn nào đó trong chuỗi bằng đoạn khác, replace giúp bạn đổi nhanh gọn.
String text = "Hello, world!";
String newText = text.replace("world", "Java"); // Kết quả: "Hello, Java!"
indexOf(String str) — tìm vị trí xuất hiện
Tìm xem chuỗi con xuất hiện lần đầu ở đâu trong chuỗi chính. Nếu không tìm thấy, hàm trả về -1.
String text = "Hello, world!";
int index = text.indexOf("world"); // Kết quả: 7
"world" bắt đầu từ vị trí số 7.
isEmpty() — kiểm tra chuỗi rỗng
Muốn kiểm tra xem chuỗi có rỗng không (không chứa ký tự nào), dùng isEmpty.
String emptyText = "";
boolean isEmpty = emptyText.isEmpty(); // Kết quả: true
Khi nào nên dùng StringBuilder?
Vì String là immutable, mỗi phép nối chuỗi đều sinh ra một đối tượng String mới. Nối vài lần thì không sao, nhưng nối hàng nghìn lần trong vòng lặp — chẳng hạn khi bạn dựng một payload lớn hay ghép một báo cáo kiểm thử — sẽ tạo ra hàng nghìn đối tượng rác và làm chậm chương trình. Khi đó, hãy dùng StringBuilder, một đối tượng có thể thay đổi (mutable) được thiết kế riêng cho việc này.
| Tiêu chí | String | StringBuilder |
|---|---|---|
| Khả năng thay đổi | Immutable — mỗi sửa đổi tạo object mới | Mutable — sửa trực tiếp trên cùng object |
| Hiệu năng khi nối nhiều lần | Chậm, tốn bộ nhớ (sinh nhiều rác) | Nhanh, tiết kiệm bộ nhớ |
| Phù hợp khi | Chuỗi cố định, nối ít lần | Nối/sửa chuỗi nhiều lần, trong vòng lặp |
Kết luận
Với String, hãy ghi nhớ ba điều cốt lõi: nó immutable, so sánh nội dung phải dùng .equals() chứ không phải ==, và khi nối chuỗi nhiều lần thì nên chuyển sang StringBuilder. Nắm chắc bộ phương thức cơ bản ở trên, bạn đã có đủ "vũ khí" để xử lý chuỗi một cách chính xác — nền tảng vững chắc cho cả lập trình lẫn viết kịch bản kiểm thử tự động.
Bạn đã từng "dính" bug nào vì so sánh chuỗi sai cách chưa? Hãy chia sẻ trải nghiệm của bạn để cộng đồng cùng học hỏi và tránh lặp lại những cái bẫy kinh điển này.