Serializable
đã hoàn thành công việc của nó và điều gì không thích về việc thực hiện tự động toàn bộ quy trình? Và những ví dụ chúng tôi đã xem xét cũng không phức tạp. Vậy vấn đề là gì? Tại sao chúng ta cần một giao diện khác cho các nhiệm vụ cơ bản giống nhau? Thực tế là Serializable
có một số thiếu sót. Chúng tôi liệt kê một số trong số họ:
-
Hiệu suất. Giao
Serializable
diện có nhiều ưu điểm, nhưng hiệu suất cao rõ ràng không phải là một trong số đó.Đầu tiên,
Serializable
triển khai nội bộ của nó tạo ra một lượng lớn thông tin dịch vụ và tất cả các loại dữ liệu tạm thời.Thứ hai,
Serializable
dựa vào API Reflection (bạn không cần phải tìm hiểu sâu về điều này ngay bây giờ; bạn có thể đọc thêm khi rảnh rỗi, nếu bạn quan tâm). Điều này cho phép bạn làm những điều dường như không thể trong Java: ví dụ: thay đổi giá trị của các trường riêng tư. CodeGym có một bài viết xuất sắc về Reflection API . Bạn có thể đọc về nó ở đó. -
Uyển chuyển. Chúng tôi không kiểm soát quá trình tuần tự hóa-giải tuần tự hóa khi chúng tôi sử dụng
Serializable
giao diện.Một mặt, nó rất thuận tiện, bởi vì nếu chúng ta không đặc biệt quan tâm đến hiệu suất, thì thật tuyệt khi không phải viết mã. Nhưng 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 (chúng tôi sẽ cung cấp một ví dụ bên dưới) vào logic tuần tự hóa thì sao?
Về cơ bản, tất cả những gì chúng ta phải kiểm soát quy trình là
transient
từ khóa để loại trừ một số dữ liệu. Đó là nó. Đó là toàn bộ hộp công cụ của chúng tôi :/ -
Bảo vệ. Mục này xuất phát một phần từ mục trước đó.
Chúng ta chưa từng dành nhiều thời gian để nghĩ về điều này trước đây, nhưng nếu một số thông tin trong lớp học của bạn không dành cho tai mắt tò mò của người khác thì sao? Một ví dụ đơn giản là mật khẩu hoặc dữ liệu người dùng cá nhân khác, trong thế giới ngày nay được điều chỉnh bởi rất nhiều luật.
Nếu chúng tôi 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ư nó vốn có.Nhưng nếu làm đúng cách, chúng ta phải mã hóa loại dữ liệu này trước khi ghi vào tệp hoặc gửi qua mạng. Nhưng
Serializable
không làm cho điều này có thể.
Externalizable
giao diện.
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;
// ...constructor, getters, setters, 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 có những thay đổi đáng kể! Điều chính là rõ ràng: khi triển khai Externalizable
giao diện, 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 đó, trách nhiệm tuần tự hóa và giải tuần tự hóa sẽ thuộc về người lập trình. Nhưng bây giờ bạn có thể giải quyết vấn đề không kiểm soát quá trình! Toàn bộ quá trình được lập trình trực tiếp bởi bạn. Đương nhiên, điều này cho phép một cơ chế linh hoạt hơn nhiều. Ngoài ra, vấn đề về bảo mật đã được giải quyết. Như bạn có thể thấy, lớp của chúng tôi có một trường dữ liệu cá nhân không thể lưu trữ mà không được mã hóa. Bây giờ chúng ta có thể dễ dàng viết mã thỏa mãn ràng buộc này. Ví dụ: chúng ta có thể thêm vào lớp của mình hai phương thức riêng tư đơn giản để mã hóa và giải mã dữ liệu nhạy cảm. Chúng tôi sẽ ghi dữ liệu vào tệp và đọc nó từ tệp ở dạng mã hóa. Phần còn lại của dữ liệu sẽ được ghi và đọc như vậy :) Kết quả là lớp của chúng ta 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 thức sử dụng các tham số ObjectOutput
và ObjectInput
tham số giống nhau mà chúng tôi đã gặp trong bài học 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 yêu cầu và chúng tôi sử dụng dữ liệu được mã hóa để tuần tự hóa đối tượng của mình. Hãy xem điều này 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("Paul", "Piper", "Paul Piper's passport data");
objectOutputStream.writeObject(userInfo);
objectOutputStream.close();
}
}
Trong các phương thức encryptString()
và decryptString()
, chúng tôi đã thêm cụ thể đầu ra của bảng điều khiển để xác minh biểu mẫu trong đó dữ liệu bí mật sẽ được ghi và đọc. Đoạn mã trên hiển thị dòng sau: SXZhbiBJdmFub3YncyBwYXNzcG9ydCBkYXRh Mã hóa thành công! Toàn bộ nội dung của tệp trông giống như sau: ¬н sr UserInfoГ!}ҐџC‚ћ xpt Ivant Ivanovt $SXZhbiBJdmFub3YncyBwYXNzcG9ydCBkYXRhx Bây giờ, hãy thử sử dụng logic khử lưu huỳnh của chúng tôi.
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();
}
}
Vâng, không có gì có vẻ phức tạp ở đây. Nó sẽ hoạt động! Chúng tôi chạy nó và nhận được... 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! :( Rõ ràng, nó không dễ dàng như vậy! Cơ chế khử lưu huỳnh đã đư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. Với , Serializable
chúng tôi đã vượt qua mà không cần một... :/ Ở đây chúng tôi đã gặp phả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 ở quyền truy cập 'mở rộng' của lập trình viên và khả năng kiểm soát quy trình linh hoạt hơn, mà còn ở chính quy trình. Trên hết, ở cơ chế khử lưu huỳnh . Khi sử dụngSerializable
, bộ nhớ được cấp phát đơn giản cho đối tượng, sau đó các giá trị được đọc từ luồng và được sử dụng để đặt các trường của đối tượng. Nếu chúng ta sử dụng Serializable
, hàm tạo của đối tượng không được gọi! Tất cả công việc diễn ra thông qua phản ánh (API phản chiếu, mà chúng tôi đã đề cập ngắn gọn trong bài học trước). Với Externalizable
, cơ chế khử lưu huỳnh là khác nhau. Hàm tạo mặc định được gọi đầu tiên. Chỉ sau đó, phương thức UserInfo
của đối tượng đã tạo mới readExternal()
được gọi. Nó chịu trách nhiệm thiết lập các trường của đối tượng. Đó là lý do tại sao bất kỳ lớp nào triển khai Externalizable
giao diện đều phải có hàm tạo mặc định . Hãy thêm một vào UserInfo
lớp của chúng ta 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 bảng điều khiển: Dữ liệu hộ chiếu của Paul Piper UserInfo \ firstName = 'Paul', lastName = 'Piper', superSecretInformation = 'Dữ liệu hộ chiếu của Paul Piper' } Bây giờ là một cái gì đó hoàn toàn khác! Đầu tiên, chuỗi được giải mã với thông tin bí mật được hiển thị trên bảng điều khiển. Sau đó, đối tượng chúng tôi đã khôi phục từ tệp được hiển thị dưới dạng một chuỗi! Vì vậy, 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 học đã có từ lâu. Và còn rất nhiều điều nữa chúng tôi chưa đề cập đến! Vẫn còn nhiều điều tinh tế liên quan khi sử dụng từng giao diện này. Nhưng để tránh làm não bạn bị nổ tung vì quá nhiều 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 cho bạn các liên kết để đọc thêm. Vì vậy, những gì khác bạn cần phải biết? Đầu tiên , trong quá trình tuần tự hóa (bất kể bạn đang sử dụng Serializable
hay Externalizable
), hãy chú ý đến static
các biến. Khi bạn sử dụng Serializable
, các trường này hoàn toàn không được đánh số thứ tự (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 bạn sử dụngExternalizable
, bạn tự kiểm soát quy trình, vì vậy về mặt kỹ thuật, bạn có thể sắp xếp chúng theo thứ tự. Tuy nhiên, chúng tôi không khuyên bạn nên làm như vậy vì có khả năng tạo ra nhiều lỗi nhỏ. Thứ hai , bạn cũng nên chú ý đến các biến có final
bổ ngữ. Khi bạn sử dụng Serializable
, chúng được tuần tự hóa và giải tuần tự hóa như bình thường, nhưng khi bạn sử dụng Externalizable
, không thể giải tuần tự hóa một final
biến ! 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 — sau đó, giá trị của chúng không thể thay đổi. Do đó, để tuần tự hóa các đối tượng có final
các trường, hãy sử dụng tuần tự hóa tiêu chuẩn được cung cấp bởi Serializable
. Thứ ba , khi bạn sử dụng tính kế thừa, tất cả các lớp hậu duệ kế thừa một sốExternalizable
lớp cũng phải có hàm tạo mặc định. Đây là liên kết đến bài viết hay về cơ chế tuần tự hóa:
Cho đến lần sau! :)