CodeGym /Blog Java /Ngẫu nhiên /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
CHÀO! Hôm nay chúng ta sẽ tiếp tục tìm hiểu về tuần tự hóa và giải tuần tự hóa các đối tượng Java . Trong bài học trước, chúng ta đã biết về giao diện đánh dấu Có thể tuần tự hóa , đã xem xét các ví dụ về việc sử dụng giao diện này và cũng đã học cách bạn có thể sử dụng từ khóa tạm thời để kiểm soát quá trình tuần tự hóa. Chà, nói rằng chúng tôi 'kiểm soát quá trình' có thể là cường điệu hóa nó. Chúng tôi có một từ khóa, một mã định danh phiên bản và chỉ có thế. Phần còn lại của quá trình được ẩn bên trong Java và chúng tôi không thể truy cập nó. Tất nhiên, về mặt tiện lợi, điều này là tốt. Nhưng một lập trình viên không nên chỉ được hướng dẫn bởi sự thoải mái của chính mình, phải không? :) Có những yếu tố khác mà bạn cần xem xét. Đó là lý do tại sao Serializablekhông phải là cơ chế duy nhất để giải 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 Externalizable . Nhưng trước khi chúng ta bắt đầu 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ơ chế khác? 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à Serializablecó một số thiếu sót. Chúng tôi liệt kê một số trong số họ:
  1. Hiệu suất. Giao Serializablediệ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ố đó.

    Giới thiệu giao diện Externalizable - 2

    Đầ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ó ở đó.

  2. 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 Serializablegiao 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à transienttừ 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 :/

  3. 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 Serializablekhông làm cho điều này có thể.

Giới thiệu giao diện Externalizable - 3Chà, cuối cùng chúng ta hãy xem lớp sẽ trông như thế nào nếu chúng ta sử dụng Externalizablegiao 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 Externalizablegiao diện, bạ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 đó, 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ố ObjectOutputObjectInputtham 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()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ệ Giới thiệu giao diện Externalizable - 4 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 , Serializablechú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 SerializableExternalizablekhô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 UserInfocủ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 Externalizablegiao diện đều phải có hàm tạo mặc định . Hãy thêm một vào UserInfolớ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 Serializablehay Externalizable), hãy chú ý đến staticcá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ì staticcá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ó finalbổ 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 finalbiến ! 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 — sau đó, giá trị của chúng không thể thay đổi. Do đó, để tuần tự hóa các đối tượng có finalcá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ốExternalizablelớ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! :)
Bình luận
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION