Serializable
Tôi đã hoàn thành công việc của mình và việc thực hiện tự động toàn bộ quá trình không thể không vui mừng. Các ví dụ chúng tôi đã xem xét cũng không phức tạp. Vậy thỏa thuận là gì? Tại sao lại có một giao diện khác cho cùng một nhiệm vụ? Thực tế là Serializable
nó có một số nhược điểm. Hãy liệt kê một số trong số họ:
-
Hiệu suất. Giao diện có
Serializable
nhiều ưu điểm nhưng hiệu năng cao rõ ràng không phải là một trong số đó.
Thứ nhất , cơ chế bên trong Serializable
tạo ra một lượng lớn thông tin dịch vụ và nhiều loại dữ liệu tạm thời trong quá trình hoạt động.
Thứ hai (bạn không cần phải đọc ngay bây giờ và đọc lúc rảnh rỗi nếu quan tâm), công việc này Serializable
dựa trên việc sử dụng API Reflection. Thiết bị này cho phép bạn thực hiện những việc dường như không thể thực hiện được trong Java: ví dụ: thay đổi giá trị của các trường riêng tư. JavaRush có một bài viết rất hay về Reflection API , bạn có thể đọc về nó ở đây.
-
Uyển chuyển. Chúng tôi hoàn toàn không kiểm soát quá trình tuần tự hóa-giải tuần tự hóa khi sử dụng
Serializable
.Một mặt, điều này rất thuận tiện, vì nếu chúng ta không thực sự quan tâm đến hiệu suất thì khả năng không viết mã có vẻ thuận tiện. Nhưng điều gì sẽ xảy ra nếu chúng ta thực sự cần thêm một số tính năng của riêng mình (ví dụ về một trong số chúng sẽ ở bên dưới) vào logic tuần tự hóa?
Về cơ bản, tất cả những gì chúng tôi phải kiểm soát quy trình là một từ khóa
transient
để loại trừ một số dữ liệu, thế là xong. Giống như một "bộ công cụ":/ -
Sự an toàn. Điểm này một phần tiếp theo từ điểm trước.
Chúng ta chưa từng nghĩ nhiều về điều này trước đây, nhưng điều gì sẽ xảy ra nếu một số thông tin trong lớp của bạn không dành cho “tai người khác” (chính xác hơn là mắt)? Một ví dụ đơn giản là mật khẩu hoặc dữ liệu cá nhân khác của người dùng, trong thế giới hiện đại được quản lý bởi rất nhiều luật.
Sử dụng
Serializable
, chúng tôi thực sự không thể làm bất cứ điều gì về nó. Chúng tôi tuần tự hóa mọi thứ như cũ.Tuy nhiên, theo một cách tốt, chúng ta phải mã hóa loại dữ liệu này trước khi ghi nó vào một tệp hoặc truyền nó qua mạng. Nhưng
Serializable
nó không mang lại cơ hội này.
Externalizable
.
import java.io.Externalizable;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectOutput;
public class UserInfo implements Externalizable {
private String firstName;
private String lastName;
private String superSecretInformation;
private static final long SERIAL_VERSION_UID = 1L;
//...конструктор, геттеры, сеттеры, toString()...
@Override
public void writeExternal(ObjectOutput out) throws IOException {
}
@Override
public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
}
}
Như bạn có thể thấy, chúng tôi đã thực hiện những thay đổi đáng kể! Điều chính là hiển nhiên: khi triển khai một giao diện, Externalizable
bạn phải triển khai hai phương thức bắt buộc - writeExternal()
và readExternal()
. Như chúng tôi đã nói trước đó, mọi trách nhiệm về tuần tự hóa và giải tuần tự hóa sẽ thuộc về người lập trình. Tuy nhiên, bây giờ bạn có thể giải quyết vấn đề thiếu kiểm soát quá trình này! Toàn bộ quá trình được bạn trực tiếp lập trình, điều này tất nhiên tạo ra một cơ chế linh hoạt hơn nhiều. Ngoài ra, vấn đề bảo mật cũng được giải quyết. Như bạn có thể thấy, chúng ta có một trường trong lớp: dữ liệu cá nhân không thể được lưu trữ khi không được mã hóa. Bây giờ chúng ta có thể dễ dàng viết mã đáp ứng ràng buộc này. Ví dụ: thêm hai phương thức riêng tư đơn giản vào lớp của chúng tôi để mã hóa và giải mã dữ liệu bí mật. Chúng tôi sẽ ghi chúng vào một tệp và đọc chúng từ tệp ở dạng được mã hóa. Và chúng ta sẽ viết và đọc phần còn lại của dữ liệu :) Kết quả là lớp của chúng ta sẽ trông giống như thế này:
import java.io.Externalizable;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectOutput;
import java.util.Base64;
public class UserInfo implements Externalizable {
private String firstName;
private String lastName;
private String superSecretInformation;
private static final long serialVersionUID = 1L;
public UserInfo() {
}
public UserInfo(String firstName, String lastName, String superSecretInformation) {
this.firstName = firstName;
this.lastName = lastName;
this.superSecretInformation = superSecretInformation;
}
@Override
public void writeExternal(ObjectOutput out) throws IOException {
out.writeObject(this.getFirstName());
out.writeObject(this.getLastName());
out.writeObject(this.encryptString(this.getSuperSecretInformation()));
}
@Override
public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
firstName = (String) in.readObject();
lastName = (String) in.readObject();
superSecretInformation = this.decryptString((String) in.readObject());
}
private String encryptString(String data) {
String encryptedData = Base64.getEncoder().encodeToString(data.getBytes());
System.out.println(encryptedData);
return encryptedData;
}
private String decryptString(String data) {
String decrypted = new String(Base64.getDecoder().decode(data));
System.out.println(decrypted);
return decrypted;
}
public String getFirstName() {
return firstName;
}
public String getLastName() {
return lastName;
}
public String getSuperSecretInformation() {
return superSecretInformation;
}
}
Chúng tôi đã triển khai hai phương pháp sử dụng các tham số giống nhau ObjectOutput out
và như ObjectInput
chúng tôi đã gặp trong bài giảng về Serializable
. Vào đúng thời điểm, chúng tôi mã hóa hoặc giải mã dữ liệu cần thiết và ở dạng này, chúng tôi sử dụng dữ liệu đó để tuần tự hóa đối tượng của mình. Hãy xem điều này sẽ trông như thế nào trong thực tế:
import java.io.*;
public class Main {
public static void main(String[] args) throws IOException, ClassNotFoundException {
FileOutputStream fileOutputStream = new FileOutputStream("C:\\Users\\Username\\Desktop\\save.ser");
ObjectOutputStream objectOutputStream = new ObjectOutputStream(fileOutputStream);
UserInfo userInfo = new UserInfo("Ivan", "Ivanov", "Ivan Ivanov's passport data");
objectOutputStream.writeObject(userInfo);
objectOutputStream.close();
}
}
Trong encryptString()
và các phương thức decryptString()
, chúng tôi đã thêm cụ thể đầu ra vào bảng điều khiển để kiểm tra xem dữ liệu bí mật sẽ được ghi và đọc ở dạng nào. Đoạn mã trên xuất ra dòng sau tới bảng điều khiển: SXZhbiBJdmFub3YncyBwYXNzcG9ydCBkYXRh Mã hóa thành công! Nội dung đầy đủ của tệp trông như thế này: ин sr UserInfoГ!}ҐџC‚ћ xpt Ivant Ivanovt $SXZhbiBJdmFub3YncyBwYXNzcG9ydCBkYXRhx Bây giờ, hãy thử sử dụng logic giải tuần tự hóa mà chúng tôi đã viết.
public class Main {
public static void main(String[] args) throws IOException, ClassNotFoundException {
FileInputStream fileInputStream = new FileInputStream("C:\\Users\\Username\\Desktop\\save.ser");
ObjectInputStream objectInputStream = new ObjectInputStream(fileInputStream);
UserInfo userInfo = (UserInfo) objectInputStream.readObject();
System.out.println(userInfo);
objectInputStream.close();
}
}
Chà, có vẻ như không có gì phức tạp ở đây cả, nó sẽ hoạt động được! Hãy chạy... Ngoại lệ trong luồng "chính" java.io.InvalidClassException: UserInfo; không có hàm tạo hợp lệ Rất tiếc :( Hóa ra nó không đơn giản như vậy! Cơ chế giải tuần tự hóa đã đưa ra một ngoại lệ và yêu cầu chúng tôi tạo một hàm tạo mặc định. Tôi tự hỏi tại sao? Chúng Serializable
tôi đã quản lý mà không có nó... :/ Ở đây chúng ta đến với một sắc thái quan trọng khác Sự khác biệt giữa Serializable
và Externalizable
không chỉ nằm ở khả năng truy cập “mở rộng” cho người lập trình và khả năng quản lý quy trình linh hoạt hơn mà còn nằm ở chính quy trìnhSerializable
đó . được phân bổ cho một đối tượng, sau đó các giá trị được đọc từ luồng, điền vào tất cả các trường của nó. Nếu chúng ta sử dụng Serializable
, hàm tạo đối tượng sẽ không được gọi! Tất cả công việc được thực hiện thông qua phản chiếu (API phản chiếu, mà chúng tôi đã đề cập ngắn gọn ở phần trước bài giảng). Trong trường hợp , Externalizable
cơ chế deserialization sẽ khác. Lúc đầu, hàm tạo mặc định được gọi. Và chỉ sau đó mới UserInfo
được gọi trên phương thức đối tượng đã tạo readExternal()
, phương thức này chịu trách nhiệm điền vào các trường của đối tượng. Đó là tại sao bất kỳ lớp nào triển khai giao diện Externalizable
đều phải có hàm tạo mặc định . Hãy thêm nó vào lớp của chúng ta UserInfo
và chạy lại mã:
import java.io.*;
public class Main {
public static void main(String[] args) throws IOException, ClassNotFoundException {
FileInputStream fileInputStream = new FileInputStream("C:\\Users\\Username\\Desktop\\save.ser");
ObjectInputStream objectInputStream = new ObjectInputStream(fileInputStream);
UserInfo userInfo = (UserInfo) objectInputStream.readObject();
System.out.println(userInfo);
objectInputStream.close();
}
}
Đầu ra của bảng điều khiển: Dữ liệu hộ chiếu của Ivan Ivanov UserInfo{firstName='Ivan', LastName='Ivanov', superSecretInformation='Dữ liệu hộ chiếu của Ivan Ivanov'} Một vấn đề hoàn toàn khác! Đầu tiên, chuỗi được giải mã với dữ liệu bí mật được xuất ra bảng điều khiển và sau đó đối tượng của chúng tôi được khôi phục từ tệp ở định dạng chuỗi! Đây là cách chúng tôi giải quyết thành công tất cả các vấn đề :) Chủ đề tuần tự hóa và giải tuần tự hóa có vẻ đơn giản, nhưng như bạn có thể thấy, các bài giảng của chúng tôi hóa ra lại rất dài. Và đó không phải là tất cả! Có nhiều điều tinh tế hơn khi sử dụng từng giao diện này, nhưng để bây giờ bộ não của bạn không bị nổ tung trước lượng thông tin mới, tôi sẽ liệt kê ngắn gọn một số điểm quan trọng hơn và cung cấp các liên kết để đọc thêm. Vậy bạn còn cần biết gì nữa? Đầu tiên , khi sắp xếp theo thứ tự (không quan trọng bạn sử dụng Serializable
hay Externalizable
), hãy chú ý đến các biến static
. Khi được sử dụng, Serializable
các trường này hoàn toàn không được tuần tự hóa (và theo đó, giá trị của chúng không thay đổi, vì static
các trường thuộc về lớp chứ không phải đối tượng). Nhưng khi sử dụng nó, Externalizable
bạn tự mình kiểm soát quá trình nên về mặt kỹ thuật, điều này có thể thực hiện được. Nhưng điều này không được khuyến khích vì điều này có nhiều lỗi nhỏ. Thứ hai , cũng cần chú ý đến các biến có bổ ngữ final
. Khi sử dụng thì Serializable
chúng được serialize và deserialize như bình thường, nhưng khi sử dụng thì không thể Externalizable
deserialize final
một biến được ! Lý do rất đơn giản: tất cả final
các trường - được khởi tạo khi hàm tạo mặc định được gọi và sau đó giá trị của chúng không thể thay đổi. Do đó, để tuần tự hóa các đối tượng chứa final
trường -, hãy sử dụng tuần tự hóa tiêu chuẩn thông qua Serializable
. Thứ ba , khi sử dụng tính kế thừa, tất cả các lớp kế thừa từ một Externalizable
lớp nào đó cũng phải có hàm tạo mặc định. Dưới đây là một số liên kết đến các bài viết hay về cơ chế tuần tự hóa:
Thấy bạn! :)
GO TO FULL VERSION