CodeGym /Java Blog /Toto sisi /Java 中的可外部化接口
John Squirrels
等級 41
San Francisco

Java 中的可外部化接口

在 Toto sisi 群組發布
你好!今天我們繼續了解Java對象的序列化和反序列化。在上一課中,我們了解了Serializable標記接口,回顧了它的使用示例,還學習瞭如何使用transient關鍵字來控制序列化過程。好吧,說我們“控製過程”可能言過其實了。我們有一個關鍵字,一個版本標識符,僅此而已。其餘的過程隱藏在Java內部,我們無法訪問它。當然,就方便性而言,這很好。但是程序員不應該只以自己的舒適為指導,對吧?:) 您還需要考慮其他因素。這就是為什麼可序列化不是 Java 中序列化-反序列化的唯一機制。今天我們將熟悉Externalizable接口。但在我們開始研究它之前,您可能會有一個合理的問題:為什麼我們需要另一種機制?Serializable完成了它的工作,為什麼不喜歡整個過程的自動執行呢?我們看到的例子也不復雜。所以有什麼問題?為什麼我們需要另一個接口來完成本質上相同的任務?事實上,它Serializable有幾個缺點。我們列出了其中的一些:
  1. 表現。接口Serializable有很多優點,但高性能顯然不是其中之一。

    Externalizable 接口介紹 - 2

    首先, Serializable的內部實現會產生大量的服務信息和各種臨時數據。

    其次, Serializable依賴於 Reflection API(您現在不必深入研究;如果您有興趣,可以在閒暇時閱讀更多內容)。這個東西可以讓你做 Java 中看似不可能的事情:例如,改變私有字段的值。CodeGym 有一篇關於反射 API 的優秀文章。你可以在那裡閱讀它。

  2. 靈活性。我們在使用接口時不控制序列化-反序列化過程Serializable

    一方面,它非常方便,因為如果我們不是特別關心性能,那麼不用編寫代碼似乎很好。但是,如果我們真的需要向序列化邏輯添加一些我們自己的功能(我們將在下面提供示例)怎麼辦?

    基本上,我們所要控制的過程就是transient排除一些數據的關鍵字。就是這樣。那是我們的整個工具箱:/

  3. 安全。此項目部分源自上一個項目。

    我們之前沒有花太多時間思考這個問題,但是如果你課堂上的某些信息不是為了讓別人窺探,那該怎麼辦?一個簡單的例子是密碼或其他個人用戶數據,在當今世界,這些數據受一系列法律管轄。

    如果我們使用Serializable,我們真的無能為力。我們按原樣序列化所有內容。

    但如果我們以正確的方式進行,我們必須在將此類數據寫入文件或通過網絡發送之前對其進行加密。但Serializable不會使這成為可能。

Externalizable 接口介紹 - 3好吧,最後讓我們看看如果我們使用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;

   // ...constructor, getters, setters, toString()...

   @Override
   public void writeExternal(ObjectOutput out) throws IOException {

   }

   @Override
   public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {

   }
}
如您所見,我們發生了重大變化!主要的一個很明顯:實現Externalizable接口時,必須實現兩個必需的方法:writeExternal()readExternal(). 正如我們之前所說,序列化和反序列化的責任在於程序員。但是現在可以解決進程無法控制的問題了!整個過程由您直接編程。自然地,這允許更靈活的機制。此外,安全問題也得到解決。如您所見,我們班級有一個個人數據字段,不能未加密存儲。現在我們可以輕鬆編寫滿足此約束的代碼。例如,我們可以在我們的類中添加兩個簡單的私有方法來加密和解密敏感數據。我們將數據寫入文件並以加密形式從文件中讀取。其餘數據將按原樣寫入和讀取 :) 結果,我們的類看起來像這樣:

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;
   }
}
我們實現了兩種方法,它們使用我們在關於 的課程中已經遇到的 相同參數ObjectOutput和參數。在適當的時候,我們加密或解密所需的數據,並使用加密的數據序列化我們的對象。讓我們看看這在實踐中是怎樣的: ObjectInputSerializable

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();

   }
}
encryptString()decryptString()方法中,我們專門添加了控制台輸出以驗證寫入和讀取秘密數據的形式。上面的代碼顯示如下一行: SXZhbiBJdmFub3YncyBwYXNzcG9ydCBkYXRh 加密成功!文件的完整內容如下所示: ¬н sr UserInfoÓ!}͐џC‚ћ xpt Ivant Ivanovt $SXZhbiBJdmFub3YncyBwYXNzcG9ydCBkYXRhx 現在讓我們嘗試使用我們的反序列化邏輯。

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();

   }
}
好吧,這裡似乎沒有什麼複雜的。它應該工作! 我們運行它並得到... Exception in thread "main" java.io.InvalidClassException: UserInfo; 沒有有效的構造 Externalizable 接口介紹 - 4函數 :( 顯然,這不是那麼容易!反序列化機制拋出一個異常並要求我們創建一個默認構造函數。我想知道為什麼。有了Serializable,我們沒有一個……:/ 這裡我們遇到了另一個重要的細微差別。Serializable和 的區別Externalizable不僅在於程序員“擴展”的訪問權限和更靈活地控制進程的能力,還在於進程本身。最重要的是反序列化機制。當使用Serializable,簡單地為對象分配內存,然後從流中讀取值並用於設置對象的字段。如果我們使用Serializable,則不會調用對象的構造函數!所有的工作都是通過反射(反射 API,我們在上節課中簡要提到過)進行的。有了Externalizable,反序列化機制就不同了。默認構造函數首先被調用。只有在那之後才會調用創建的UserInfo對象的readExternal()方法。它負責設置對象的字段。這就是為什麼任何實現該Externalizable接口的類都必須有默認構造函數的原因。讓我們在我們的UserInfo類中添加一個並重新運行代碼:

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();
   }
}
控制台輸出: Paul Piper 的護照數據 UserInfo \ firstName = 'Paul', lastName = 'Piper', superSecretInformation = 'Paul Piper's passport data' } 現在完全不同了!首先,解密後的帶有秘密信息的字符串顯示在控制台上。然後我們從文件中恢復的對象顯示為字符串!所以我們已經成功解決了所有問題:) 序列化和反序列化的主題看似簡單,但是,正如您所看到的,課程很長。 還有更多我們沒有涵蓋的內容!使用這些接口中的每一個時,仍然涉及許多微妙之處。但是為了避免過多的新信息讓您的大腦爆炸,我將簡要列出一些更重要的要點,並為您提供額外閱讀的鏈接。那麼,您還需要了解什麼? 首先,在序列化期間(無論您使用的Serializable是 還是Externalizable),請注意 static變量。當您使用 時Serializable,這些字段根本不會序列化(因此,它們的值不會改變,因為static字段屬於類,而不屬於對象)。但是當你使用Externalizable,你自己控制這個過程,所以從技術上講你可以序列化它們。但是,我們不推薦這樣做,因為這樣做可能會產生很多細微的錯誤。 其次,還應注意帶有final修飾符的變量。當你使用的時候Serializable,它們像往常一樣被序列化和反序列化,但是當你使用的時候,反序列化一個變量Externalizable是不可能的final!原因很簡單:final當調用默認構造函數時,所有字段都會被初始化——之後,它們的值將無法更改。因此,要序列化具有final字段的對象,請使用Serializable. 第三,當你使用繼承時,所有繼承一些的後代類Externalizable類還必須具有默認構造函數。這是有關序列化機制的好文章的鏈接: 直到下一次!:)
留言
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION