제이온

[Hiệu quả Java] Mục 6. Tránh tạo đối tượng không cần thiết

  • Ngôn ngữ viết: Tiếng Hàn Quốc
  • Quốc gia: Tất cả các quốc giacountry-flag
  • CNTT

Đã viết: 2024-04-28

Đã viết: 2024-04-28 13:40

Trong trường hợp tạo ra các đối tượng không cần thiết

Sử dụng new String()


Các chuỗi a, b, c đều có giá trị là chuỗi “hi”. Tuy nhiên, địa chỉ mà ba chuỗi này tham chiếu đến đều khác nhau, dẫn đến việc phân bổ bộ nhớ riêng biệt cho cùng một dữ liệu, gây lãng phí.


<span class="image-inline ck-widget" contenteditable="false"><img src="https://www.notion.so/image/https%3A%2F%2Fs3-us-west-2.amazonaws.com%2Fsecure.notion-static.com%2Fa53d14aa-6abf-40f0-8441-435647d172fa%2FUntitled.png?table=block&id=f168859c-367c-4924-a4c6-5e04788fab67&spaceId=b453bd85-cb15-44b5-bf2e-580aeda8074e&width=2000&userId=80352c12-65a4-4562-9a36-2179ed0dfffb&cache=v2" alt="Untitled" style="aspect-ratio:2000/1146;" width="2000" height="1146"></span>


Vì vậy, khi khai báo chuỗi, chúng ta không nên sử dụng từ khóa new mà nên khai báo bằng literal.



Đoạn mã nguồn ở trên chỉ sử dụng một instance duy nhất. Thêm vào đó, nếu sử dụng cách này, tất cả các đoạn mã sử dụng chuỗi literal “hi” trong cùng một JVM sẽ đảm bảo tái sử dụng cùng một đối tượng. Điều này là do đặc điểm của Java Constant Pool.


Sử dụng new Boolean()

Đoạn mã trên tạo ra một instance Boolean thông qua constructor nhận chuỗi làm tham số. Boolean chỉ có thể là true hoặc false, việc tạo instance mỗi lần sẽ gây lãng phí bộ nhớ. Vì vậy, nên sử dụng phương thức factory tĩnh Boolean.valueOf().



Sử dụng String.matches()

Nếu chi phí tạo đối tượng lớn, chúng ta nên lưu vào bộ nhớ cache để tái sử dụng. Tuy nhiên, không phải lúc nào chúng ta cũng biết được chi phí tạo đối tượng của mình. Ví dụ, nếu muốn viết một phương thức kiểm tra xem một chuỗi có phải là số La Mã hợp lệ hay không, chúng ta có thể sử dụng biểu thức chính quy như sau:



Tuy nhiên, String.matches() là một phương thức có vấn đề về hiệu năng. Phương thức này tạo ra instance Pattern cho biểu thức chính quy bên trong, sau khi sử dụng xong sẽ bị thu hồi bởi bộ thu gom rác. Nếu tần suất sử dụng biểu thức chính quy này cao, chi phí tạo và thu hồi instance Pattern sẽ tăng lên. Vì vậy, tốt hơn hết là nên lưu instance Pattern vào bộ nhớ cache và tái sử dụng nó mỗi khi phương thức isRomanNumeral() được gọi.



Lưu ý

Trong tất cả các ví dụ trên, khi lưu vào bộ nhớ cache các đối tượng không cần thiết, chúng ta đều tạo ra các đối tượng bất biến. Điều này là để đảm bảo an toàn khi tái sử dụng. Tuy nhiên, có những trường hợp trái ngược với trực giác khi tái sử dụng các đối tượng bất biến.


Adapter (view) là một đối tượng đóng vai trò như một giao diện thứ hai, trong khi công việc thực sự được ủy thác cho đối tượng phía sau. Adapter chỉ cần quản lý đối tượng phía sau, vì vậy chúng ta chỉ cần tạo ra một adapter cho mỗi đối tượng phía sau.


Ví dụ, phương thức keySet() của giao diện Map trả về một view Set chứa tất cả các key trong đối tượng Map. Người dùng có thể nghĩ rằng mỗi khi gọi phương thức keySet(), một instance Set mới sẽ được tạo ra. Tuy nhiên, nếu xem xét cách triển khai thực tế trong JDK, chúng ta sẽ thấy rằng cùng một instance Set có thể thay đổi được được trả về mỗi lần.


Điều này là do chức năng của tất cả các instance Set đều giống nhau và tất cả các instance Set đều đại diện cho instance Map. Vì vậy, việc keySet() tạo ra nhiều view đối tượng không thành vấn đề, nhưng cũng không cần thiết và không mang lại lợi ích gì.



Do đó, nếu sửa đổi instance names1, instance names2 cũng sẽ bị ảnh hưởng.


Tuy nhiên, cá nhân tôi cho rằng phương thức keySet() nên sử dụng kỹ thuật sao chép phòng thủ để trả về một đối tượng mới mỗi lần. Nếu instance Set nhận được từ phương thức keySet() cũng được sử dụng ở những nơi khác và có mã thay đổi trạng thái của instance này, chúng ta sẽ không thể chắc chắn về trạng thái của instance Set hiện tại và instance Map.


Hơn nữa, trừ khi môi trường sử dụng keySet() quá nhiều, việc tạo ra giao diện Set mỗi lần sẽ không ảnh hưởng đáng kể đến hiệu năng. Thay vào đó, tôi cho rằng việc tạo ra giao diện Set thành đối tượng bất biến sẽ tốt hơn cho việc bảo trì và ổn định.


Autoboxing

Autoboxing là một kỹ thuật tự động chuyển đổi giữa kiểu dữ liệu cơ bản và kiểu dữ liệu wrapper khi lập trình viên trộn lẫn chúng. Tuy nhiên, autoboxing chỉ làm mờ ranh giới giữa kiểu dữ liệu cơ bản và kiểu dữ liệu wrapper, chứ không loại bỏ hoàn toàn chúng.



Về mặt logic, đoạn mã này không có vấn đề gì. Tuy nhiên, nó rất kém hiệu quả về mặt hiệu năng. Nguyên nhân là do kiểu dữ liệu của sum và i trong vòng lặp for.


Kiểu dữ liệu của sum là Long, và kiểu dữ liệu của i là long. Điều này có nghĩa là mỗi khi i được cộng vào sum trong vòng lặp, một instance Long mới sẽ được tạo ra. Kết quả là, chúng ta nên sử dụng kiểu dữ liệu cơ bản thay vì kiểu dữ liệu wrapper và cẩn thận để tránh autoboxing không mong muốn.


Những điểm cần lưu ý

Chúng ta không nên hiểu nhầm rằng việc tránh tạo ra các đối tượng không cần thiết đơn giản chỉ là do chi phí tạo đối tượng lớn.


Đặc biệt là trong các JVM hiện đại, việc tạo và thu hồi các đối tượng nhỏ không cần thiết không phải là một tác vụ tốn kém. Vì vậy, trừ khi đối tượng có chi phí tạo rất lớn, chẳng hạn như kết nối cơ sở dữ liệu, chúng ta không nên tự tạo ra pool đối tượng.


Hơn nữa, hãy nhớ rằng hậu quả của việc thất bại trong việc sao chép phòng thủ khi tái sử dụng đối tượng có thể nghiêm trọng hơn nhiều so với việc tạo ra các đối tượng không cần thiết một cách lặp đi lặp lại. Hậu quả của việc tạo ra các đối tượng lặp đi lặp lại chỉ ảnh hưởng đến hình dạng của mã và hiệu năng, nhưng nếu sao chép phòng thủ thất bại, nó sẽ dẫn đến lỗi và vấn đề bảo mật.


Nguồn tham khảo

Bình luận0

[Phi chuyên ngành, trở thành Developer] 14. Tóm tắt những câu hỏi kỹ thuật thường gặp trong phỏng vấn tuyển dụng Developer mớiBài viết này tóm tắt những câu hỏi kỹ thuật thường gặp trong phỏng vấn tuyển dụng Developer mới (vùng nhớ, cấu trúc dữ liệu, cơ sở dữ liệu, v.v.). Hy vọng bài viết sẽ giúp ích cho quá trình chuẩn bị phỏng vấn của bạn.
투잡뛰는 개발 노동자
투잡뛰는 개발 노동자
투잡뛰는 개발 노동자
투잡뛰는 개발 노동자

April 3, 2024