Hế lô anh em ✌️✌️✌️
Chúng ta đều biết Java là ngôn ngữ lập trình hướng đối tượng mà hướng đối tượng thì mọi thứ sẽ xoay quanh các objects.
Đối với objects trong Java chúng ta có hai khái niệm là mutable (có thể thay đổi) và immutable (bất biến) không biết anh em biết không!?
Ngoài ra có một câu hỏi phỏng vấn liên quan đến chủ đề này mà mình nghĩ các bạn intern hoặc fresher thường được hỏi đó là: "Tại sao String được thiết kế là immutable trong Java?"
Bài viết hôm nay mình sẽ cùng anh em trả lời những câu hỏi này nhé. Let's move!✈️✈️✈️
1. Thế nào là Mutable Objects?
Mutable objects là những đối tượng có trạng thái nội tại hay nói cách khác là giá trị của đối tượng đó có thể thay đổi được sau khi khởi tạo.
Ví dụ một vài class trong Java như: java.util.Date
, StringBuilder
, StringBuffer
...
Đối với mutable objects khi chúng ta thực hiện cập nhật các giá trị nội tại thì bản chất không có đối tượng nào được tạo ra thêm cả. Thay vào đó sẽ chỉ cập nhật các giá trị nội tại của đối tượng đã tồn tại mà thôi.
Class của đối tượng sẽ cung cấp các phương thức để hỗ trợ việc thay đổi các giá trị nội tại đó. Tiêu biểu là các hàm setters()
để cập nhật giá trị và getter()
để đọc thông tin.
2. Làm sao tạo một Mutable Class?
Với những gì mình đề cập ở phần 1 chắc anh em cũng hiểu thế nào là một mutable object rồi. Câu hỏi là làm sao để tạo một mutable object?
Cũng đơn giản thôi, mình nghĩ class kiểu này anh em tạo rất nhiều rồi!
Ví dụ:
public class TpBank {
private String fullName;
private Double balance;
TpBank(String fullName, Double balance) {
this.fullName = fullName;
this.balance = balance;
}
public String getFullName() {
return fullName;
}
public void setFullName(String fullName) {
this.fullName = fullName;
}
public Double getBalance() {
return balance;
}
public void setBalance(Double balance) {
this.balance = balance;
}
}
TpBank tpBank = new TpBank("Ngân hàng Thương mại Cổ phần Tiên Phong", 2100922.15);
System.out.println("Tên: " + tpBank.getFullName() + " | Số dư: " + tpBank.getBalance());
tpBank.setFullName("Ngân hàng Thương mại Cổ phần Quân Đội");
tpBank.setBalance(2300925.15);
System.out.println("Tên: " + tpBank.getFullName() + " | Số dư: " + tpBank.getBalance());
Output:
Tên: Ngân hàng Thương mại Cổ phần Tiên Phong | Số dư: 2100922.15
Tên: Ngân hàng Thương mại Cổ phần Quân Đội | Số dư: 2300925.15
Rõ ràng ở đây chúng ta có thể sử dụng các hàm setters()
để thay đổi giá trị nội tại của object và khi đó object này được gọi là mutable object.
3. Thế nào là Immutable Objects?
Ngược lại với mutable object thì immutable object là những đối tượng có trạng thái nội tại hay nói cách khác là giá trị của đối tượng không thể thay đổi được sau khi khởi tạo.
Ví dụ: Các giá trị kiểu primitive types (int
, long
, float
, double
...), các legacy classes (HashTable
, Stack
, Vector
...), các wrapper classes (Integer
, Long
, Double
...) hay điển hình nhất là String
class.
Nếu như đối với các mutable object chúng ta có thể sử dụng các hàm setters()
để thay đổi giá trị nội tại của đối tượng thì với các immutable object chỉ có thể sử dụng getters()
để lấy ra thông tin đối tượng.
4. Làm sao tạo một Immutable Class?
Để tạo một immutable class chúng ta có một vài lưu ý như sau:
+ Tất cả các trường dữ liệu (fields) nên để chế độ truy cập (access modifier) là private để tránh việc bị truy cập tới từ bên ngoài class.
+ Không khai báo các hàm setters()
+ Tất cả các trường dữ liệu (fields) có thể thay đổi nên được khai báo final
để tránh việc bị lặp lại sau khi được khởi tạo.
Ví dụ:
public final class TpBank {
private final String fullName;
private Double balance;
TpBank(final String fullName, Double balance) {
this.fullName = fullName;
this.balance = balance;
}
public String getFullName() {
return fullName;
}
public Double getBalance() {
return balance;
}
public void setBalance(Double balance) {
this.balance = balance;
}
}
TpBank tpBank = new TpBank("Ngân hàng Thương mại Cổ phần Tiên Phong", 2100922.15);
System.out.println("Tên: " + tpBank.getFullName() + " | Số dư: " + tpBank.getBalance());
tpBank.setBalance(2300925.15);
System.out.println("Tên: " + tpBank.getFullName() + " | Số dư: " + tpBank.getBalance());
Output:
Tên: Ngân hàng Thương mại Cổ phần Tiên Phong | Số dư: 2100922.15
Tên: Ngân hàng Thương mại Cổ phần Tiên Phong | Số dư: 2300925.15
Ở đây mình đã không khai báo hàm setter()
cho thuộc tính fullName và đồng thời sử dụng từ khóa final
khi khai báo trường dữ liệu này.
Vì vậy đối với thuộc tính fullName chúng ta chỉ có thể sử dụng hàm getter()
để lấy thông tin và khi giá trị của trường này được khởi tạo (thông qua constructor) thì nó sẽ không thể thay đổi được nữa.
5. Tại sao String trong Java lại Immutable?
Đây là một câu hỏi cũng rất hay được hỏi khi chúng ta đi phỏng vấn, đặc biệt là đối với những anh em phỏng vấn ở các vị trí như intern hay fresher.
Vậy tại sao đối tượng String trong Java lại được thiết kế là immutable?
Để trả lời câu hỏi này chúng ta có thể điểm qua một vài ý chính như sau:
5.1 - String Pool
String Pool là một vùng nhớ đặc biệt nơi các đối tượng String được lưu trữ và quản lý bởi JVM (Java Virtual Machine). String cũng là một kiểu dữ liệu được sử dụng rất nhiều trong thực tế.
Do đó việc caching hay tái sử dụng sẽ giúp tiết kiệm được rất nhiều không gian bộ nhớ Heap vì các biến kiểu String sẽ tham chiếu tới cùng một đối tượng trong String pool nếu chúng có cùng giá trị.
Nhưng vai trò thực sự của String pool là gì?
Trước Java 7, thực tế String pool được khởi tạo và quản lý bởi JVM tại một vùng nhớ có tên là PermGem với kích thước cố định. Điều này dẫn đến việc kích thước của String pool không thể được mở rộng ở thời điểm runtime, không có cơ chế garbage collection (dọn rác bộ nhớ) và dễ bị lỗi OutOfMemory.
Từ Java 7 trở đi, String pool được khai báo và khởi tạo ở vùng nhớ Heap và có cơ chế garbage collection nên đã giảm thiểu tối đa việc dẫn đến lỗi OutOfMemory.
Tất nhiên chỉ là giảm thiểu nên thực tế việc sử dụng không đúng hoàn toàn vẫn có thể dẫn đến lỗi và hãy tưởng tượng sẽ ra sao nếu String không phải là immutable?
Khi đó giá trị của biến String có thể được thay đổi sau khi khởi tạo (thường là ở quá trình runtime mà chúng ta không lường trước được). Ví dụ đơn giản nhất là trường hợp cộng chuỗi sẽ tạo ra một đối tượng String mới và dẫn tới tăng kích thước của String pool.
Ví dụ:
String s1 = "laptrinhb2a";
String s2 = ".com";
String s3 = s1 + s2;
String s4 = "laptrinhb2a.com";
System.out.println(s1.hashCode());
System.out.println(s2.hashCode());
System.out.println(s3.hashCode());
System.out.println(s4.hashCode());
-1741013465
1469075
1462189370
1462189370
Anh em có thể xem hình bên dưới để dễ hình dung hơn.
Có thể kết luận chính việc String được thiết kế là immutable đã dẫn đến đến sự ra đời của String pool với mục đích tối ưu hóa việc khởi tạo và lưu trữ một trong những đối tượng được sử dụng nhiều nhất trong Java đó là String.
5.2 - Bảo mật (Security)
String thường được sử dụng để lưu trữ các thông tin bảo mật như usernames, passwords, connection URLs, network connections...
Vì thế việc bảo mật các đối tượng String là rất quan trọng vì nó ảnh hưởng trực tiếp tới tính bảo mật của ứng dụng. Anh em cùng mình xem ví dụ bên dưới và phân tích nhé!
void updateUserByUsername(String username) {
// kiểm tra xem có phải là chuỗi không
if (!isAlphaNumeric(username)) {
throw new SecurityException();
}
// kết nối cơ sở dữ liệu
initializeDatabase();
// cập nhật thông tin database
connection.executeUpdate("UPDATE Users SET Status = 'Active' WHERE UserName = '" + username + "'");
}
Trong ví dụ trên phương thức của chúng ta nhận vào một tham số từ bên ngoài và rõ ràng việc gọi hàm, truyền tham số như vậy không thể đảm bảo tính tin cậy của tham số truyền vào (trong ví dụ này là tham số username)
Bây giờ mình giả sử String là mutable bằng cách mô phỏng nó thông qua class StringCustom
như sau:
public class StringCustom {
private String value;
public String getValue() {
return value;
}
public void setValue(String value) {
this.value = value;
}
}
Tiếp đó mình sẽ dùng class này là tham số cho phương thức updateUserByUsername()
bên trên:
void updateUserByUsername(StringCustom username) {
// kiểm tra xem có phải là chuỗi không
if (!isAlphaNumeric(username.getValue())) {
throw new SecurityException();
}
// thay đổi giá trị của biến username
username.setValue("NULL OR UserName IS NOT NULL");
// kết nối cơ sở dữ liệu
initializeDatabase();
// cập nhật thông tin database
connection.executeUpdate("UPDATE Users SET Status = 'Active' WHERE UserName = '" + username.getValue() + "'");
}
Rõ ràng ở đây chúng ta sẽ gặp phải một lỗi bảo mật rất kinh điển đó là SQL injections khi giá trị của biến username bị thay đổi mặc dù đã được kiểm tra ở các bước trước.
Đây cũng là một trong những lý do String được thiết kế là immutable để giá trị của nó không thể bị thay đổi và đảm bảo an toàn cho việc lưu trữ các thông tin nhạy cảm.
5.3 - Đồng bộ hóa (Synchronization)
Đa luồng (multiple threads) giúp cho chương trình đạt hiệu suất cao hơn trong nhiều trường hợp nhưng cũng có nhiều trường hợp nếu không thể đồng bộ dữ liệu ở các threads khác nhau thì chương trình sẽ chạy sai.
Nếu String được thiết kế không phải là immutable thì giữa các threads rất dễ sảy ra trường hợp khiến cho giá trị của đối tượng String bị thay đổi.
Nhưng String được thiết kế là immutable nên chúng ta hoàn toàn đạt được mong muốn thread-safe khi làm việc với String trong môi trường multiple threads vì nếu có một thread nào đấy thay đổi giá trị thì ngay lập tức một đối tượng mới được tạo ra mà không liên quan gì đến đối tượng cũ.
5.4 - Hiệu năng (performance)
Như đã đề cập trong các phần ở trên, hiệu năng là một trong những ưu điểm rất lớn khi String được thiết kế là immutable.
Cũng chính vì String là immutable nên String pool mới được sinh ra để tối ưu việc việc lưu trữ và cấp phát bộ nhớ cho các đối tượng String.
Ngoài ra String cũng là đối tượng đồng thời là kiểu dữ liệu được sử dụng rất nhiều trong các chương trình nên String được thiết kế là immutable sẽ giúp tăng hiệu năng của chương trình nếu chúng ta sử dụng String đúng cách.
Note: Trong thực tế việc String được thiết là immutable object còn rất nhiều lợi ích khác nhưng ở đây mình chỉ liệt kê ra một vài lợi ích tiêu biểu nhất.
6. Lời kết
Vậy là trong bài viết hôm nay mình đã cùng anh em tìm hiểu về hai khái niệm khá đơn giản nhưng cũng rất quan trọng của các ngôn ngữ lập trình hướng đối tượng như Java đó là mutable và immutable.
Qua đó đồng thời trả lời câu hỏi tại sao đối tượng String trong Java lại được thiết là immutable. Hẹn gặp lại anh em trong các bài viết tiếp theo nhé!
Tham khảo:
https://www.baeldung.com/java-string-immutable
https://www.javatpoint.com/mutable-and-immutable-in-java
Thanks all ❤️❤️❤️
Không có nhận xét nào: