JavaRush /Blog Java /Random-VI /Giao diện có thể mở rộng trong Java

Giao diện có thể mở rộng trong Java

Xuất bản trong nhóm
Xin chào! Hôm nay chúng ta sẽ tiếp tục giới thiệu về việc tuần tự hóa và giải tuần tự hóa các đối tượng Java. Trong bài giảng trước, chúng ta đã được giới thiệu về giao diện điểm đánh dấu Serializable , xem xét các ví dụ về cách sử dụng nó và cũng đã học cách kiểm soát quá trình tuần tự hóa bằng cách sử dụng từ khóa tạm thời . Tất nhiên, “quản lý quy trình” là một từ mạnh mẽ. Chúng tôi có một từ khóa, một ID phiên bản và về cơ bản là như vậy. Phần còn lại của quá trình được “cố định” bên trong Java và không có quyền truy cập vào nó. Từ quan điểm thuận tiện, điều này tất nhiên là tốt. Nhưng một lập trình viên trong công việc của mình không chỉ nên tập trung vào sự thoải mái của bản thân, phải không? :) Có những yếu tố khác cần xem xét. Do đó, Serializable không phải là công cụ duy nhất để tuần tự hóa-giải tuần tự hóa trong Java. Hôm nay chúng ta sẽ làm quen với giao diện Bên ngoài . Nhưng ngay cả trước khi chúng ta chuyển sang nghiên cứu nó, bạn có thể có một câu hỏi hợp lý: tại sao chúng ta cần một công cụ khác? SerializableTô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à Serializablenó có một số nhược điểm. Hãy liệt kê một số trong số họ:
  1. Hiệu suất. Giao diện có Serializablenhiều ưu điểm nhưng hiệu năng cao rõ ràng không phải là một trong số đó.

Giới thiệu Giao diện ngoài - 2

Thứ nhất , cơ chế bên trong Serializabletạ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 Serializabledự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.

  1. 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ụ":/

  2. 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 Serializablenó không mang lại cơ hội này.

Giới thiệu Giao diện bên ngoài - 3Chà, cuối cùng chúng ta hãy xem một lớp sẽ trông như thế nào khi sử dụng 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, Externalizablebạn phải triển khai hai phương thức bắt buộc - writeExternal()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 outvà như ObjectInputchú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ệ Giới thiệu Giao diện ngoài - 4 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 Serializabletô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 SerializableExternalizablekhô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 , Externalizablecơ 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 UserInfovà 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 Serializablehay Externalizable), hãy chú ý đến các biến static. Khi được sử dụng, Serializablecá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ì staticcác trường thuộc về lớp chứ không phải đối tượng). Nhưng khi sử dụng nó, Externalizablebạ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ì Serializablechúng được serialize và deserialize như bình thường, nhưng khi sử dụng thì không thể Externalizabledeserialize finalmột biến được ! Lý do rất đơn giản: tất cả finalcá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 finaltrườ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 Externalizablelớ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! :)
Bình luận
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION