如何使用可外部化的接口在Java中自定义序列化
在上一篇文章“用示例介紹的有關(guān)Java序列化的一切”中 ,我解釋了如何使用以下方法序列化/反序列化一個(gè)對象
Serializable接口,還說明了如何使用writeObject和readObject方法自定義序列化過程。
Java序列化過程的缺點(diǎn)
但是,這些定制還不夠,因?yàn)镴VM可以完全控制序列化過程,而這些定制邏輯只是默認(rèn)序列化過程的補(bǔ)充。 我們?nèi)匀槐仨毻ㄟ^從writeObject和ObjectInputStream.defaultReadObject()調(diào)用ObjectOutputStream.defaultWriteObject()和ObjectInputStream.defaultReadObject()來使用默認(rèn)的序列化邏輯。
readObject方法。 如果不調(diào)用這些默認(rèn)方法,我們的對象將不會被序列化/反序列化。
默認(rèn)的序列化過程是完全遞歸的。 因此,每當(dāng)我們嘗試序列化一個(gè)對象時(shí),序列化過程都會嘗試使用我們的類( static和static除外)對所有字段(原始和引用)進(jìn)行序列化。
transient場)。 這使得序列化過程非常緩慢。
現(xiàn)在,我們假設(shè)我們有一個(gè)對象,其中包含很多字段,由于某些原因,我們不想序列化這些字段(這些字段將始終分配有默認(rèn)值)。 在默認(rèn)的序列化過程中,我們將不得不使所有這些字段都是瞬態(tài)的,但是它仍然不會高效,因?yàn)閷⑦M(jìn)行大量檢查以查看這些字段是否為瞬態(tài)的。
因此,如我們所見,使用默認(rèn)序列化過程有很多弊端,例如:
什么是外部化和外部化接口
正如我們在上面看到的,默認(rèn)的Java序列化效率不高。 我們可以通過使用Externalizable接口而不是
Serializable接口。
我們可以通過實(shí)現(xiàn)
可外部化的接口并覆蓋它的方法writeExternal()和
readExternal() 。 但是使用這種方法,我們將無法從JVM獲得任何類型的默認(rèn)序列化邏輯,而是由我們來提供完整的序列化和反序列化邏輯。
因此,非常仔細(xì)地對測試這些方法進(jìn)行編碼非常有必要,因?yàn)檫@可能會破壞序列化過程。 但是,如果正確實(shí)現(xiàn),與默認(rèn)序列化過程相比,外部化過程非常快。
我們將以下面的Employee類對象為例進(jìn)行說明:
// Using Externalizable, complete serialization/deserialization logic becomes our responsibility, // We need to tell what to serialize using writeExternal() method and what to deserialize using readExternal(), // We can even serialize/deserialize static and transient variables, // With implementation of writeExternal() and readExternal(), methods writeObject() and readObject() becomes redundant and they do not get called. Employee class implements Externalizable { // This serialVersionUID field is necessary for Serializable as well as Externalizable to provide version control, // Compiler will provide this field if we do not provide it which might change if we modify class structure of our class, and we will get InvalidClassException, // If we provide a value to this field and do not change it, serialization-deserialization will not fail if we change our class structure. private static final long serialVersionUID = 2L; private String firstName; private transient String lastName; // Using Externalizable, we can even serialize/deserialize transient variables, so declaring fields transient becomes unnecessary. private int age; private static String department; // Using Externalizable, we can even serialize/deserialize static variables according to our need. // Mandatory to have to make our class Externalizable // When an Externalizable object is reconstructed, the object is created using public no-arg constructor before the readExternal method is called. // If a public no-arg constructor is not present then a InvalidClassException is thrown at runtime. public Employee() { } // All-arg constructor to create objects manually public Employee(String firstName, String lastName, int age, String department) { this .firstName = firstName; this .lastName = lastName; this .age = age; Employee.department = department; validateAge(); } private void validateAge() { System.out.println( "Validating age." ); if (age < 18 || age > 70 ) { throw new IllegalArgumentException( "Not a valid age to create an employee" ); } } @Override // We need to tell what to serialize in writeExternal() method public void writeExternal(ObjectOutput out) throws IOException { System.out.println( "Custom externalizable serialization logic invoked." ); out.writeUTF(firstName); out.writeUTF(lastName); out.writeInt(age); out.writeUTF(department); } @Override // We need to tell what to deserialize in readExternal() method // The readExternal method must read the values in the same sequence and with the same types as were written by writeExternal public void readExternal(ObjectInput in) throws IOException { System.out.println( "Custom externalizable serialization logic invoked." ); firstName = in.readUTF(); lastName = in.readUTF(); age = in.readInt(); department = in.readUTF(); validateAge(); } @Override public String toString() { return String.format( "Employee {firstName='%s', lastName='%s', age='%s', department='%s'}" , firstName, lastName, age, department); } // Custom serialization logic, It will be called only if we have implemented Serializable instead of Externalizable. private void writeObject(ObjectOutputStream oos) throws IOException { System.out.println( "Custom serialization logic invoked." ); } // Custom deserialization logic, It will be called only if we have implemented Serializable instead of Externalizable. private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException { System.out.println( "Custom deserialization logic invoked." ); } }序列化如何與可外部化接口一起工作
如上面在示例Employee類中所見,我們可以通過實(shí)現(xiàn)Externalizable接口并覆蓋其方法writeExternal()和readExternal()來編寫自己的序列化邏輯。
通過調(diào)用DataOutput方法的原始值或調(diào)用ObjectOutput對象的writeObject方法的對象,字符串和數(shù)組,該對象可以實(shí)現(xiàn)writeExternal方法來保存其內(nèi)容。
通過調(diào)用原始類型的DataInput方法和對象,字符串和數(shù)組的readObject方法,該對象可以實(shí)現(xiàn)readExternal方法以恢復(fù)其內(nèi)容。 readExternal方法必須按與writeExternal相同的順序和相同的類型讀取值。
// We need to tell what fields to serialize in writeExternal() method public void writeExternal(ObjectOutput out) throws IOException { System.out.println( "Custom externalizable serialization logic invoked." ); out.writeUTF(firstName); out.writeUTF(lastName); out.writeInt(age); out.writeUTF(department); } // We need to tell what fields to deserialize in readExternal() method // The readExternal method must read the values in the same sequence and with the same types as were written by writeExternal public void readExternal(ObjectInput in) throws IOException { System.out.println( "Custom externalizable serialization logic invoked." ); firstName = in.readUTF(); lastName = in.readUTF(); age = in.readInt(); department = in.readUTF(); validateAge(); } 要將對象序列化和反序列化為文件,我們需要遵循與Serializable示例相同的過程,這意味著調(diào)用
如以下代碼所示,完成ObjectOutputStream.writeObject()和ObjectInputStream.readObject() :
Externalizable接口是Serializable的子接口,即
Externalizable extends Serializable 。 因此,如果我們實(shí)現(xiàn)Externalizable接口并覆蓋其writeExternal()和
然后,將使用readExternal()方法優(yōu)先于這些方法,而不是由JVM提供的默認(rèn)序列化機(jī)制。 這些方法取代了writeObject和readObject方法的定制實(shí)現(xiàn),因此,如果我們還提供writeObject()和readObject() ,則將忽略它們。
在序列化過程中,將針對要序列化的每個(gè)對象的Externalizable接口進(jìn)行測試。 如果對象支持Externalizable,則調(diào)用writeExternal方法。 如果對象不支持Externalizable并且實(shí)現(xiàn)了Serializable,則使用ObjectOutputStream保存該對象。
重建Externalizable對象時(shí),將使用公共no-arg構(gòu)造函數(shù)創(chuàng)建一個(gè)實(shí)例,然后調(diào)用readExternal方法。 可序列化的對象通過從ObjectInputStream讀取來恢復(fù)。
Externalizable實(shí)例可以通過Serializable接口中記錄的writeReplace和readResolve方法指定替換對象。
Java 序列化還可以用于深度克隆對象 。 Java克隆是Java社區(qū)中最有爭議的話題,它的確有其缺點(diǎn),但是在對象完全滿足Java克隆的強(qiáng)制條件之前,它仍然是創(chuàng)建對象副本的最流行和最簡單的方法。 我在3篇文章的Java克隆系列中詳細(xì)介紹了克隆 ,其中包括Java克隆和克隆類型(淺和深)等文章, 并帶有示例 , Java克隆–復(fù)制構(gòu)造器與克隆 , Java克隆–甚至復(fù)制構(gòu)造器都不是如果您想了解更多有關(guān)克隆的知識,請充分閱讀它們。
可外部化與可序列化之間的差異
讓我們列出Java中Externalizable和Serializable接口之間的主要區(qū)別。
您可以在此找到本文的完整源代碼。
Github存儲庫 ,請隨時(shí)提供寶貴的反饋。
翻譯自: https://www.javacodegeeks.com/2019/08/customize-serialization-java-using-externalizable-interface.html
總結(jié)
以上是生活随笔為你收集整理的如何使用可外部化的接口在Java中自定义序列化的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 旅游鞋怎样清洗才干净 清理旅游鞋的方法
- 下一篇: 关于Jakarta EE与MicroPr