Tại sao String là Immutable trong Java?

1. Giới thiệu

Trong Java, các chuỗi là bất biến. Một câu hỏi hiển nhiên khá phổ biến trong các cuộc phỏng vấn là "Tại sao các chuỗi được thiết kế như là bất biến trong Java?"

James Gosling, người sáng tạo ra Java, đã từng được hỏi trong một cuộc phỏng vấn khi nào thì nên sử dụng bất biến, ông trả lời:

Tôi sẽ sử dụng bất biến bất cứ khi nào tôi có thể.

Ông ủng hộ thêm lập luận của mình nêu rõ các tính năng mà tính bất biến cung cấp, chẳng hạn như bộ nhớ đệm, bảo mật, dễ dàng sử dụng lại mà không cần sao chép, v.v.

Trong hướng dẫn này, chúng ta sẽ khám phá thêm lý do tại sao các nhà thiết kế ngôn ngữ Java quyết định giữ cho Chuỗi bất biến.

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

Một đố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 tạo hoàn toàn . Điều này có nghĩa là một khi đối tượng đã được gán cho một biến, chúng ta không thể cập nhật tham chiếu hoặc thay đổi trạng thái bên trong bằng bất kỳ phương tiện nào.

Chúng tôi có một bài viết riêng thảo luận chi tiết về các đối tượng bất biến. Để biết thêm thông tin, hãy đọc bài viết Các đối tượng bất biến trong Java.

3. Tại sao String Immutable trong Java?

Các lợi ích chính của việc giữ cho lớp này là bất biến là bộ nhớ đệm, bảo mật, đồng bộ hóa và hiệu suất.

Hãy thảo luận về cách những thứ này hoạt động.

3.1. Giới thiệu với Chuỗi Pool

Các chuỗi là cấu trúc dữ liệu sử dụng rộng rãi nhất. Lưu vào bộ đệm các chuỗi ký tự và sử dụng lại chúng tiết kiệm rất nhiều không gian đống vì các biến Chuỗi khác nhau tham chiếu đến cùng một đối tượng trong nhóm chuỗi . Nhóm thực tập chuỗi phục vụ chính xác mục đích này.

Java String Pool là vùng bộ nhớ đặc biệt nơi các Chuỗi được lưu trữ bởi JVM . Vì các Chuỗi là bất biến trong Java, JVM tối ưu hóa lượng bộ nhớ được phân bổ cho chúng bằng cách chỉ lưu trữ một bản sao của mỗi Chuỗi theo nghĩa đen trong nhóm. Quá trình này được gọi là thực tập:

String s1 = "Hello World"; String s2 = "Hello World"; assertThat(s1 == s2).isTrue();

Do sự hiện diện của nhóm chuỗi trong ví dụ trước, hai biến khác nhau đang trỏ đến cùng một đối tượng Chuỗi từ nhóm, do đó tiết kiệm tài nguyên bộ nhớ quan trọng.

Chúng tôi có một bài viết riêng dành riêng cho Java String Pool. Để biết thêm thông tin, hãy xem bài viết đó.

3.2. Bảo vệ

Các chuỗi được sử dụng rộng rãi trong các ứng dụng Java để lưu trữ phần nhạy cảm của thông tin như tên người dùng, mật khẩu, URL kết nối, kết nối mạng, vv Nó cũng được sử dụng rộng rãi bởi các bộ tải lớp JVM trong khi các lớp tải.

Do đó bảo mật lớp String 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ụ: hãy xem xét đoạn mã đơn giản này:

void criticalMethod(String userName) { // perform security checks if (!isAlphaNumeric(userName)) { throw new SecurityException(); } // do some secondary tasks initializeDatabase(); // critical task connection.executeUpdate("UPDATE Customers SET Status = 'Active' " + " WHERE UserName = '" + userName + "'"); }

Trong đoạn mã trên, giả sử rằng chúng tôi đã nhận được một đối tượng Chuỗi từ một nguồn không đáng tin cậy. Ban đầu, chúng tôi đang thực hiện tất cả các kiểm tra bảo mật cần thiết để kiểm tra xem Chuỗi chỉ là chữ và số, sau đó là một số thao tác khác.

Hãy nhớ rằng phương thức người gọi nguồn không đáng tin cậy của chúng tôi vẫn có tham chiếu đến đối tượng userName này .

Nếu các Chuỗi có thể thay đổi, thì vào thời điểm chúng tôi thực hiện cập nhật, chúng tôi không thể chắc chắn rằng Chuỗi mà chúng tôi nhận được, ngay cả sau khi thực hiện kiểm tra bảo mật, sẽ an toàn. Phương thức người gọi không đáng tin cậy vẫn có tham chiếu và có thể thay đổi Chuỗi giữa các lần kiểm tra tính toàn vẹn. Do đó, làm cho truy vấn của chúng ta dễ bị chèn SQL trong trường hợp này. Vì vậy, các Chuỗi có thể thay đổi có thể dẫn đến sự suy giảm bảo mật theo thời gian.

Cũng có thể xảy ra trường hợp String userName hiển thị cho một luồng khác, sau đó có thể thay đổi giá trị của nó sau khi kiểm tra tính toàn vẹn.

Nói chung, tính bất biến giải cứu được chúng ta trong trường hợp này bởi vì việc vận hành với mã nhạy cảm dễ dàng hơn khi các giá trị không thay đổi vì có ít thao tác xen kẽ hơn có thể ảnh hưởng đến kết quả.

3.3. Đồng bộ hóa

Tính bất biến tự động làm cho chuỗi Chuỗi an toàn vì chúng sẽ không bị thay đổi khi được truy cập từ nhiều chuỗi.

Do đó , các đối tượng bất biến, nói chung, 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 nhóm Chuỗi . Do đó, Chuỗi an toàn cho đa luồng.

3.4. Bộ nhớ đệm mã băm

Vì các đối tượng String được sử dụng nhiều như một cấu trúc dữ liệu, chúng cũng được sử dụng rộng rãi trong các triển khai băm như HashMap , HashTable , HashSet , v.v. Khi hoạt động dựa trên các triển khai băm này, phương thức hashCode () được gọi khá thường xuyên để bán vé.

Tính bất biến đảm bảo cho các Chuỗi rằng giá trị của chúng sẽ không thay đổi. Vì vậy, các hashCode () phương pháp được ghi đè trong Chuỗi lớp để tạo điều kiện bộ nhớ đệm, như vậy mà băm được tính toán và lưu trữ trong thời gian đầu tiên hashCode () cuộc gọi và cùng giá trị được trả về từ bao giờ.

Điều này, đến lượt nó, cải thiện hiệu suất của các tập hợp sử dụng triển khai băm khi hoạt động với các đối tượng Chuỗi .

Mặt khác, các 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 hoạt động, có khả năng làm mất đối tượng giá trị trong Bản đồ .

3.5. Hiệu suất

Như chúng ta đã thấy trước đây, String pool tồn tại vì các Chuỗi là bất biến. Đổi lại, nó nâng cao hiệu suất bằng cách tiết kiệm bộ nhớ heap và truy cập nhanh hơn các triển khai băm khi hoạt động với Chuỗi.

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, chúng ta có thể kết luận rằng các Chuỗi là bất biến chính xác để các tham chiếu của chúng có thể được coi như một biến bình thường và người ta có thể chuyển chúng xung quanh, giữa các phương thức và trên các luồng mà không cần lo lắng về việc liệu đối tượng Chuỗi thực sự mà nó trỏ đến có thay đổi hay không.

Chúng tôi cũng biết được đâu là những lý do khác thúc đẩy các nhà thiết kế ngôn ngữ Java làm cho lớp này trở thành bất biến.