Người dịch: Lê Trung Kiên lớp java 08
Bài viết gốc: https://www.baeldung.com/java-string-immutable

1. Mở đầu

String là một trong những class được sử dụng nhiều nhất trong bất kỳ ngôn ngữ lập trình nào. Chúng ta luôn nhận được câu hỏi “Tại sao String là Immutable trong Java?” khi đi phỏng vấn.
James Gosling, cha đẻ của ngôn ngữ lập trình Java, đã được hỏi rằng khi nào thì nên sử dụng Immutable (bất biến)? Ông đã trả lời rằng: “Tôi sẽ sử dụng bất biến bất cứ khi nào có thể.”
Tại sao lại sử dụng bất biến bất cứ khi nào có thể? Chúng ta sẽ cùng giải đáp câu hỏi này qua bài viết ngày hôm nay.

2. Đối tượng bất biến (Immutable Object) là gì?

Trước hết hãy cùng tìm hiểu đối tượng bất biến là gì. Đối tượng bất biến là một đối tượng có trạng thái bên trong không đổi sau khi nó được khởi tạo. Nghĩa là, khi đối tượng này được gán cho một biến, chúng ta không thể cập nhật tham chiếu, thay đổi trạng thái bên trong bằng bất kỳ cách nào.

3. Tại sao String là Immutable trong Java?

Hãy cùng xem xét một số lợi ích của tính bất biến của String, điều này sẽ giúp hiểu được lý do tại sao String là Immutable trong Java.

3.1. String pool

String là một trong những class được sử dụng nhiều nhất. Khi chuỗi được tạo ra, nó sẽ được lưu trong bộ nhớ Heap, nếu hai biến cùng được gán cho giá trị là một chuỗi, nó sẽ cùng trỏ về một vị trí, điều này giúp Java Runtime tiết kiệm rất nhiều dung lượng Heap.

Java String Pool là vùng bộ nhớ đặc biệt dùng để lưu trữ các chuỗi, hãy xem ví dụ dưới đây:

String s1 = "Hello World";
String s2 = "Hello World";
         
assertThat(s1 == s2).isTrue(); //Kết quả trả về true

Ở ví dụ trên ta thấy mặc dù có hai biến s1 và s2 nhưng chúng cùng trỏ về chuỗi “Hello World” nên hai biến này là như nhau.
Hãy đến với ví dụ tiếp theo

String s1 = "Hello World";
String s3 = new String("Hello World");
         
assertThat(s1 == s3).isTrue(); //Kết quả trả về false

Ở ví dụ này kết quả lại trả về false, tại sao? Tại vì khi này s3 không biểu thị cho chuỗi “Hello World” như s1 mà biểu thị cho object String “Hello World”.
String pool

“String là bất biến tại sao tôi vẫn cắt, nối chuỗi được?”

String s = "Hello";
s.concat(" World"); //Phương thức nối chuỗi. Kết quả mong đợi: "Hello World"
System.out.prinln(s); //Kết quả: "Hello"

Như bạn có thể thấy, nếu bạn không gán s vào biến tham chiếu mới, chuỗi “Hello” vẫn không hề thay đổi.
Hãy thử như sau:

String s = "Hello";
s = s.concat(" World"); 
System.out.prinln(s); //Kết quả: "Hello World"

“Đó, s thay đổi thành Hello World rồi còn gì?”
Đúng là biến s đã thay đổi thành “Hello World”, nhưng đây lại là chuỗi hoàn toàn khác được lưu ở String pool. Bên cạnh đó, chuỗi “Hello” vẫn còn trong bộ nhớ Heap và không hề bị thay đổi, chỉ là không có biến nào tham chiếu đến nó. Sau một khoảng thời gian nhất định, bộ nhớ sẽ tự động xóa những chuỗi không có biến nào tham chiếu đến để tiết kiệm dung lượng.

3.2. Bảo mật

Chuỗi được sử dụng rộng rãi trong các ứng dụng Java để lưu trữ các phần thông tin nhạy cảm như tên người dùng, mật khẩu, URL kết nối, kết nối mạng, v.v…
Do đó, bảo mật chuỗi là rất quan trọng liên quan đến bảo mật của toàn bộ ứng dụng nói chung. Ví dụ:

void criticalMethod(String userName) {
    // Kiểm tra bảo mật
    if (!isAlphaNumeric(userName)) {
        throw new SecurityException(); 
    }
	
    // Thực hiện các tác vụ khác
    initializeDatabase();
	
    connection.executeUpdate("UPDATE Customers SET Status = 'Active' " +
      " WHERE UserName = '" + userName + "'");
}

Ở ví dụ trên, nếu chuỗi không là bất biến, ta sẽ vô tình kích hoạt userName không đáng tin cậy kia.
Tuy nhiên, vì chuỗi là bất biến nên không thể thay đổi giá trị của nó, nếu không, bất kỳ hacker nào cũng có thể thay đổi giá trị được tham chiếu để gây ra các vấn đề bảo mật trong ứng dụng.

3.3. Đồng bộ hóa

Vì String là bất biến nên nó an toàn cho đa luồng, có thể được chia sẻ trên nhiều luồng chạy đồng thời.Chúng cũng an toàn cho luồng vì nếu một luồng thay đổi giá trị, thì thay vì sửa đổi như cũ, một chuỗi mới sẽ được tạo trong String pool.

3.4. Hashcode

Vì các đối tượng String được sử dụng nhiều làm cấu trúc dữ liệu, chúng cũng được sử dụng rộng rãi trong các Hash Implement khác như HashMap, HashTable, HashSet, v.v… Do chuỗi có tính bất biến, mã băm của nó được lưu vào bộ nhớ đệm tại thời điểm tạo và nó không cần phải tính toán lại. Điều này làm cho nó trở thành một ứng cử viên sáng giá cho key trong Map và quá trình xử lý của nó nhanh hơn so với các đối tượng khác.
Mặt khác, nếu chuỗi có thể thay đổi sẽ tạo ra hai mã băm khác nhau tại thời điểm chèn và truy xuất nếu nội dung của chuỗi được sửa đổi sau khi chạy chương trình, có khả năng làm mất đối tượng giá trị trong Map.

3.5. Hiệu suất

Như chúng ta đã thấy, String pool tồn tại là do String là bất biến. Đổi lại, hiệu suất được nâng cao bằng cách tiếp kiệt bộ nhớ Heap, quá trình xử lý các hash implement nhanh hơn.
Vì chuỗi là cấu trúc dữ liệu được sử dụng rộng rãi nhất, việc cải thiện hiệu suất của chuỗi có tác động đáng kể đến việc cải thiện hiệu suất của toàn bộ ứng dụng nói chung.

4. Kết luận

Qua bài viết này, hi vọng các bạn có thể hiểu thêm về lý do tại sao String lại bất biến trong Java. Cảm ơn các bạn đã theo dõi.