JavaRush /Java Blog /Random-TW /Java 中的外部化接口

Java 中的外部化接口

在 Random-TW 群組發布
你好!今天我們繼續介紹Java物件的序列化和反序列化。在上一講中,我們介紹了Serialized標記接口,查看了其使用範例,也學習如何使用瞬態關鍵字控制序列化過程。嗯,「管理流程」當然是一個很強烈的字。我們只有一個關鍵字、一個版本 ID,基本上就是這樣。這個過程的其餘部分是「硬連線」在 Java 內部的,無法存取它。從方便的角度來看,這當然是好的。但一個程式設計師在工作中不應該只專注於自己的舒適度,對吧?:) 還有其他因素要考慮。因此,Serialized並不是Java中唯一的序列化-反序列化工具。今天我們將熟悉Externalized介面。但即使在我們繼續研究它之前,您可能會有一個合理的問題:為什麼我們需要另一個工具?Serializable我應付了我的工作,整個流程的自動執行不能不讓我慶幸。我們看到的例子也不複雜。那麼到底是怎麼回事呢?為什麼要使用另一個介面來完成基本上相同的任務?事實是Serializable它有很多缺點。讓我們列出其中一些:
  1. 表現。此介面有Serializable很多優點,但高效能顯然不是其中之一。

外部化介面簡介 - 2

首先,內部機制Serializable在運作過程中會產生大量的服務資訊和各類臨時資料。
其次(如果您有興趣,您現在不必深入討論,可以在閒暇時閱讀),這項工作Serializable基於使用 Reflection API。這個裝置可以讓你做一些在 Java 中看似不可能的事情:例如,更改私有欄位的值。JavaRush 有一篇關於 Reflection API 的優秀文章,您可以在這裡閱讀。

  1. 靈活性。使用Serializable.

    一方面,這非常方便,因為如果我們並不真正關心效能,那麼不編寫程式碼的能力似乎很方便。但是,如果我們確實需要為序列化邏輯添加一些我們自己的功能(以下是其中之一的範例)怎麼辦?

    本質上,我們所需要控制的過程就是一個關鍵字transient來排除一些數據,僅此而已。有點像「工具包」:/

  2. 安全。這一點部分源於上一點。

    我們之前沒有考慮過這個問題,但是如果你課堂上的某些資訊不是為了「其他人的耳朵」(更準確地說,眼睛)而準備的怎麼辦?一個簡單的例子是使用者的密碼或其他個人數據,在現代世界中受到一系列法律的監管。

    使用Serializable,我們實際上無能為力。我們按原樣序列化一切。

    但是,以一種好的方式,我們必須在將此類資料寫入檔案或透過網路傳輸之前對其進行加密。但Serializable它不給這個機會。

外部化介面簡介 - 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;

   //...конструктор, геттеры, сеттеры, 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 out和作為參數。在正確的時間,我們加密或解密必要的數據,並以這種形式使用它來序列化我們的物件。讓我們看看這在實踐中會是什麼樣子: 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("Ivan", "Ivanov", "Ivan Ivanov'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();

   }
}
嗯,這裡看起來沒什麼複雜的,應該可以用! 讓我們運行... 線程“main”中的異常 java.io.InvalidClassException: UserInfo; 沒有有效的構造函數 外部化介面簡介 - 4哎呀 :( 事實證明並不是那麼簡單!反序列化機制拋出了一個異常,並要求我們創建一個默認構造函數。我想知道為什麼?Serializable我們沒有它就成功了... :/這裡我們遇到了另一個重要的細微差別SerializableExternalizable不僅在於程式設計師的「擴展」存取和更靈活管理進程的能力,還在於進程本身,首先在反序列化機制上…使用時,Serializable記憶體簡單來說就是為一個物件分配,之後從流中讀取值,填充它的所有欄位。如果我們使用Serializable,則不會呼叫物件建構函數!所有工作都是透過反射(Reflection 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();
   }
}
控制台輸出: Ivan Ivanov 的護照資料 UserInfo{firstName='Ivan', lastName='Ivanov', superSecretInformation='Ivan Ivanov 的護照資料'} 完全不同的事情!首先,解密後的帶有秘密資料的字串被輸出到控制台,然後我們的物件以字串格式從檔案中恢復出來!這就是我們成功解決所有問題的方法:) 序列化和反序列化的主題看似簡單,但正如你所看到的,我們的講座結果很長。這還不是全部!使用這些介面時還有很多微妙之處,但為了讓您的大腦不會因大量新資訊而爆炸,我將簡要列出一些更重要的要點並提供額外閱讀的連結。那麼您還需要了解什麼? 首先,序列化時(無論使用Serializableor或Externalizable),要注意變數static。使用時,Serializable這些欄位根本不會序列化(因此,它們的值不會改變,因為static這些欄位屬於類,而不是物件)。但是在使用的時候,Externalizable你自己控制這個過程,所以從技術上來說這是可以做到的。但不建議這樣做,因為這充滿了微妙的錯誤。 其次,也應注意帶有修飾符的變數final。使用時,Serializable它們像平常一樣序列化和反序列化,但是使用時,不可能Externalizable反序列化final變數!原因很簡單:所有final欄位都會在呼叫預設建構函式時初始化,之後它們的值就無法變更。因此,要序列化包含final-fields 的對象,請透過Serializable. 第三,當使用繼承時,從某個類別派生的所有繼承類別Externalizable也必須具有預設建構子。以下是一些有關序列化機制的好文章的連結: 再見!:)
留言
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION