Mục lục
Trong Java, String được thiết kế immutable nghĩa là chúng ta sẽ không thể cập nhật giá trị một String sau khi đã khởi tạo. Chúng ta sẽ thường thấy một câu hỏi phỏng vấn Java liên quan đến String rất nhiều đó là “Tại sao String lại được thiết kế là immutable?”
James Gosling, cha đẻ của ngôn ngữ Java, đã từng được hỏi trong một cuộc phỏng vấn rằng khi nào thì nên sử dụng immutable, ông trả lời:
I would use an immutable whenever I can.
Tạm dịch: Tôi sẽ sử dụng immutable bất cứ khi nào tôi có thể
Ông đưa ra một số tính năng mà immutable mang lại như cache, security, tái sử dụng v.v
Immutable Object là gì
Một immutable object là một đối tượng có trạng thái bên trong không đổi sau khi được khởi tạo. Điều này có nghĩa là một khi object đã được gán cho một biến, chúng ta sẽ không thể cập nhật giá trị của object này thông qua các biến tham chiếu.
Tại sao String là immnutable?
Các lợi ích chính của việc giữ cho lớp này là bất biến là cache, security, synchronization và performance.
String Pool
String là một cấu trúc được sử dụng rất nhiều trong Java, việc lưu chúng vào bộ đệm và tái sử dụng chúng tiết kiệm rất nhiều tài nguyên của cùng nhớ heap.
String pool là một vùng nhớ được thiết kế đặc biệt dùng để lưu trữ các String. Bởi vì String có tính chất Immutable, JVM có thể tối ưu hóa bộ nhớ bằng cách lưu trữ một bản sao giá trị của một String duy nhất trong đó và sử dụng lại khi có một String object khác khởi tạo có cùng giá trị với các String đã được lưu trong String pool.
String s1 = "Hello World"; String s2 = "Hello World"; assertThat(s1 == s2).isTrue();
Như ví dụ trên, bản chất toán tử = dùng để so sánh địa chỉ của 2 object trong Java. Mặc dù được khởi tạo giá trị riêng biệt nhưng cả 2 biến s1 và s2 đều trỏ về cùng một vùng nhớ trong String pool.
Điều này là do sau khi khởi tạo S1 = “Hello World”, giá trị này sau đó sẽ được lưu vào một vùng nhớ trong string pool, Khi khởi tạo S2 với giá trị tương tự, JVM sẽ phát hiện có một giá trị tương ứng đã được khởi tạo trước đó, thay vì khởi tạo một giá trị mới thì nó trả về địa chỉ của giá trị cũ.
Security
String được sử dụng rất nhiều trong các ứng dụng Java, nó có thể được dùng để lưu trữ các thông tin nhạy cảm như username, password, connection url v.v. Do đó, việc bảo mật String đóng vai trò quan trọng trong bảo mật toàn bộ ứng dụng.
void criticalMethod(String userName) { //Thực hiện các kiểm tra để đảm bảo tính bảo mật if (!checkSecurity(userName)) { throw new SecurityException(); } // ...... // Cập nhật dữ liệu xuống database statement.executeUpdate("UPDATE Customers SET Status = 'Active' " + " WHERE UserName = '" + userName + "'"); }
Trong đoạn mã trên, giả sử nếu chúng ta nhận một String object từ một nguồn không đáng tin cậy. Sau đó thực hiện kiểm các kiểm tra nhầm đảm bảo tính bảo mật cho chương trình.
Hãy nhớ rằng nơi gọi đến criticalMethod() vẫn còn giữ biến tham chiếu đến userName object.
Nếu String không phải là immutable, thì tại thời điểm mà chúng ta thực thi lệnh update dữ liệu, chúng ta không thể chắc rằng userName object lúc này là String object mà chúng ta đã nhận ban đầu và vượt qua tất cả các bài kiểm tra của chúng ta. Bởi vì nơi gọi hàm của chúng ta đến từ nguồn không đáng tin cậy có thể chỉnh sửa dữ liệu của String thông qua biến tham chiếu mà họ giữ trước khi gọi hàm. Điều này có thể khiến hệ thống của chúng ta dễ dàng bị tấn công thông qua SQL injection.
Cũng có thể, nếu String này được sử dụng bởi nhiều thread khác nhau thì dữ liệu cũng có thể thay đổi bởi các thread khác nhau trong quá trình thực thi.
Nếu String là immnutable, thì chúng ta có thể tránh các trường hợp lỗi, vì từ khi nhận đến khi thực thi câu lệnh update sẽ đảm bảo chúng là một giá trị duy nhất. Nếu nguồn không đáng tin cậy có thể chỉnh sửa userName thì khi nào một String mới với giá trị được thay đổi được tạo mà không ảnh hưởng đến giá trị ban đầu.
Synchronization
Immutable giúp cho String an toàn vì chúng sẽ không bị thay đổi khi được truy cập từ nhiều thread khác nhau.Do đó, các immutable objects nói chung kể cả String có thể chia sẽ giữa các thread.
Hashcode Caching
Vì String được sử dụng rất nhiều trong các ứng dụng Java, chúng cũng được sử dụng rộng rãi trong các Map implementation như HashMap, HashTable, HashSet, v.v và có lẽ hashCode() sẽ được sử dụng khá thường xuyên để tính toán các giá trị hash cho từng phần tử cụ thể.
Immutable đảm bảo cho các String rằng giá trị của chúng sẽ không thay đổi. Vì vậy, phương thức hashCode() được override trong class String để tạo điều kiện sử dụng bộ nhớ đệm. Hàm hashCode() sẽ tính toán giá trị với mỗi String nhận vào và lưu chúng vào bộ nhớ đệm và sử dụng lại trong những lần sau khi nó phải tính toán với giá trị tương tự.
Performance
Như các phần trước đã thảo luận, String pool được thiết kế để lưu trữ và tối ưu hóa vùng nhớ cho các dữ liệu kiểu String bằng việc tái sử dụng dữ liệu có sẵn. Ngoài ra các Map implementation có thể hoạt động nhanh hơn khi làm việc với String nhờ vào bộ nhớ đệm đã đề cập ở trên.
Hơn hết, String gần như là một cấu trúc dữ liệu được sử dụng nhiều nhất trong các ứng dụng Java. Việc tối ưu hiệu suất cho String cũng góp phần tối ưu hiệu suất cho toàn ứng dụng.
Nguồn