Vấn đề
Bạn đã bao giờ tự hỏi tại sao so sánh hai String bằng == đôi khi cho kết quả false dù giá trị trông giống hệt nhau? Hoặc tại sao nối chuỗi trong vòng lặp lại làm chậm ứng dụng?
Trong Java, String là một kiểu dữ liệu không thể thiếu. Với tính năng bất biến (immutable), String mang lại nhiều lợi ích nhưng cũng gây không ít khó khăn nếu không hiểu rõ cách hoạt động. Trong bài viết này, chúng ta sẽ đi sâu vào nguyên lý, cách sử dụng và tối ưu hóa String trong Java.
String là gì?
String trong Java là một đối tượng đặc biệt dùng để lưu trữ và xử lý chuỗi ký tự. Điểm nổi bật là các chuỗi trong Java là immutable (không thể thay đổi). Khi bạn thao tác trên một String, một đối tượng mới sẽ được tạo thay vì thay đổi giá trị gốc.
Ví dụ:
String str = "Thanh";

Tính bất biến này giúp cải thiện bảo mật và khả năng chia sẻ tài nguyên trong các chương trình đa luồng nhưng đôi khi gây ảnh hưởng đến hiệu suất nếu sử dụng không đúng cách.
Khởi tạo String trong Java
Có hai cách chính để khởi tạo String:
- Sử dụng Literal:
- Tạo chuỗi và lưu trực tiếp vào vùng nhớ String Pool.
String str1 = "Hello, Literal!";
String str2 = "Hello, Literal!";
System.out.println(str1 == str2); // true
- Sử dụng Constructor:
- Tạo đối tượng mới trong vùng nhớ Heap.
String str3 = new String("Hello, Constructor!");
System.out.println(str1 == str3); // false
System.out.println(str1.equals(str3)); // true
Lưu ý: Bạn có thể sử dụng phương thức intern() để chuyển chuỗi từ Heap vào String Pool:
String str4 = new String("Hello, Intern!").intern();
String Pool
String Pool là một vùng nhớ đặc biệt trong Java Heap dành riêng cho các chuỗi được tạo bằng literal. Các chuỗi giống nhau sẽ chỉ tồn tại một bản duy nhất trong pool, giúp tiết kiệm bộ nhớ và cải thiện hiệu suất.
Ưu điểm của String Pool
- Tiết kiệm bộ nhớ: Tái sử dụng chuỗi đã tồn tại.
- Tăng hiệu suất: So sánh các chuỗi trong pool nhanh hơn vì chỉ cần so sánh địa chỉ (reference).
Khi nào nên dùng Literal và Constructor?
- Literal: Khi cần tối ưu hóa bộ nhớ, đặc biệt khi làm việc với các chuỗi lặp lại.
- Constructor: Khi cần tạo chuỗi độc lập, không phụ thuộc vào các chuỗi khác.
Hiệu suất và tối ưu hóa
Sử dụng StringBuilder/StringBuffer
Vì tính bất biến, các thao tác nối chuỗi (concatenation) trên String có thể tạo ra nhiều đối tượng không cần thiết. Để khắc phục, bạn nên sử dụng StringBuilder (cho các thao tác đơn luồng) hoặc StringBuffer (cho môi trường đa luồng):
StringBuilder sb = new StringBuilder("Hello");
sb.append(", World!");
System.out.println(sb.toString());
Hạn chế sử dụng + trong vòng lặp
Thay vì sử dụng toán tử + trong vòng lặp:
String result = "";
for (int i = 0; i < 1000; i++) {
result += i;
}
Hãy sử dụng StringBuilder:
StringBuilder result = new StringBuilder();
for (int i = 0; i < 1000; i++) {
result.append(i);
}
Điều này giúp giảm đáng kể việc tạo đối tượng mới, cải thiện hiệu suất.
Text Blocks (Java 15+)
Kể từ Java 15, Text Blocks là cách chuẩn để viết chuỗi đa dòng mà không cần escape hay nối chuỗi thủ công. Cú pháp dùng ba dấu ngoặc kép """:
// Trước Java 15 — dài dòng và dễ lỗi
String json = "{\n" +
" \"name\": \"Thanh\",\n" +
" \"age\": 25\n" +
"}";
// Java 15+ với Text Blocks — sạch hơn nhiều
String json = """
{
"name": "Thanh",
"age": 25
}
""";
Text Blocks cực kỳ hữu ích khi làm việc với JSON, SQL, HTML hay bất kỳ chuỗi định dạng nào.
