JAVA序列化和反序列化:进阶要点

414 阅读6分钟

进阶要点

操作类

  • SerializaitonClass.java(序列化)

    package com.jbt;
    import java.io.FileOutputStream;
    import java.io.IOException;
    import java.io.ObjectOutputStream;
    public class SerializaitonClass {
        public static void main(String[] args) {
            Employee emp = new Employee();
            emp.firstName = "Vivekanand";
            emp.lastName = "Gautam";
            try {
                FileOutputStream fileOut = new FileOutputStream("./employee.txt");
                ObjectOutputStream out = new ObjectOutputStream(fileOut);
                out.writeObject(emp);
                out.close();
                fileOut.close();
                System.out.printf("Serialized data is saved in ./employee.txt file");
            } catch (IOException i) {
                i.printStackTrace();
            }
        }
    }
    
  • DeserializationClass.java(反序列化)

    package com.jbt;
    import java.io.*;
    public class DeserializationClass {
        public static void main(String[] args) {
            Employee emp = null;
            try {
                FileInputStream fileIn = new FileInputStream("./employee.txt");
                ObjectInputStream in = new ObjectInputStream(fileIn);
                emp = (Employee) in.readObject();
                in.close();
                fileIn.close();
            } catch (IOException i) {
                i.printStackTrace();
                return;
            } catch (ClassNotFoundException c) {
                System.out.println("Employee class not found");
                c.printStackTrace();
                return;
            }
            System.out.println("Deserializing Employee...");
            System.out.println("First Name of Employee: " + emp.firstName);
            System.out.println("Last Name of Employee: " + emp.lastName);
        }
    }
    

条件

  • 序列化接口需要使对象序列化
  • 瞬态实例变量并未以对象状态序列化。
  • 如果超类实现了Serializable,则子类也可以自动进行Serializable
  • 如果无法对超类进行序列化,则在对子类进行反序列化时,将调用超类的默认构造器。 因此,所有变量将获得默认值,引用将为null

serialVersionUID

package com.jbt;
import java.io.Serializable;
public class Employee implements Serializable
{
   public String firstName;
   
   private static final long serialVersionUID = 5462223600l;
}

serialVersionUID是通过序列化运行时与每个可序列化类关联的版本号。 在反序列化过程中使用此版本号来验证序列化对象的发送者和接收者是否已为该对象加载了与序列化兼容的类。

  • 在可序列化类中定义serialVersionUID字段不是强制性
  • 如果可序列化的类具有显式的serialVersionUID,则此字段的类型应为long,并且必须是静态且最终的。
  • 如果没有明确定义的serialVersionUID字段,则序列化运行时将计算该类的默认值。 随编译器的实现而有所不同。 因此建议定义serialVersionUID
  • 建议对serialVersionUID使用私有访问修饰符。
  • 数组类无法声明显式的serialVersionUID,因此它们始终具有默认的计算值,但是对于数组类,无需匹配serialVersionUID值。
  • 如果已加载的接收器类的serialVersionUID与相应的发送器类之间存在差异,则将引发 InvalidClassException
  • 如果要禁止序列化同一类旧版本的新类,则应对同一类的不同版本使用不同的serialVersionUID

不推荐:

@SuppressWarnings(“serial”)

如果您没有在应该序列化的类中提供serialVersionId,则编译器将给出有关该序列的警告消息。 如果要覆盖此警告,则可以使用给定的注解。 使用后,编译器将不再报错缺少的serialVersionUID

瞬态

我们可以使用Serializable保存对象的状态。 但是,如果我不想保存字段状态怎么办? 在这种情况下,可以像下面这样使用瞬态修饰符。 序列化过程中不会保存瞬态字段状态,反序列化时会将默认值分配给同一变量。

使用瞬态变量更改Employee类。

package com.jbt;
import java.io.Serializable;
public class Employee implements Serializable
{
   public String firstName;
   /*
    * Here transient modifier is used for lastName variable. 
    * This variable's state will not be saved while serialzation.
    * While De-Serialization process default value will be provide.
    * null in this case.
    */
   transient public String lastName;
   private static final long serialVersionUID = 5462223600l;
}

类层次结构和可序列化

在这里,我将讨论Serializable接口对类层次结构的影响。 如果一个类实现了Serializable接口,则可以保存该类的状态。 但是,如果同一类扩展了另一个未实现Serializable接口的类,则不会保存超类的状态。

为了了解区别,我们将更新原始的Employee类。 现在,该类将扩展另一个类superEmployee。 该超类将不会实现Serializable接口。

package com.jbt;
import java.io.Serializable;
public class Employee extends superEmployee implements Serializable {
    public String firstName;
    private static final long serialVersionUID = 5462223600l;
}
​
class superEmployee {
    public String lastName;
}
​
//执行“SerializaitonClass”和“DeserializationClass”,结果如下:
/*
Deserializing Employee...
First Name of Employee: Vivekanand
Last Name of Employee: null
*/

瞬态与静态变量与最终修饰符

静态变量(static)

静态变量属于一个类,而不属于任何单个实例。 序列化的概念与对象的当前状态有关。 仅与类的特定实例关联的数据被序列化,因此静态成员字段在序列化过程中将被忽略。

瞬态变量(transient)

如果您不想保存变量的状态,请在序列化时使用。 您必须将该变量标记为Transient。 环境将知道应忽略此变量,并且不会保存相同的值。

静态和瞬态变量的区别

public class Employee implements Serializable {
    public String firstName;
    private static final long serialVersionUID = 5462223600l;
    
    static String companyName = "TATA";
    transient String address = "DEL";
    static transient String companyCEO = "Jayshree";
}
​
//执行“SerializaitonClass”和“DeserializationClass”,结果如下:
/*
Company Name: TATA
Company Address: null
Company CEO: Jayshree
*/

在这两种情况下,此处存储的case值均来自类(Employee类),而不是对象(emp对象) 。 此外,即使是临时变量,也会保存companyCEO变量值。 因为static修饰符会更改此变量的行为。

重点:

  1. 静态变量无法序列化。

  2. 在反序列化时,如果在基类的初始化过程中提供了相同的值,则可用于静态变量。

    • 这并不意味着静态变量将被序列化。 这仅意味着静态变量将使用相同的值初始化,并在加载类时分配(在这种情况下为TATA)。 如果之前未加载类(新 JVM)。 请注意示例代码。
    • 如果类已经在 JVM 中加载并且静态变量值已更改,那么将显示更改后的值。
  3. 如果将变量定义为“静态”和“瞬态”,则静态修饰符将控制变量的行为,而不是瞬态。

    • 静态和瞬态是不同的。 在某些情况下,它们的行为看起来相同,但并非总是如此。 如果在加载类时为变量分配了一个值,则在反序列化类时该值将分配给静态变量,而不是瞬时的。 因此,如果您同时使用这两个修饰符和变量,那么从某种意义上来说,我要说的是静态将优先于瞬态。
  4. 瞬态变量值将不会保存。 同样,在反序列化过程中不能为其分配任何值。 这与静态行为不同。

最终修饰符(final)

对瞬态变量

public class Employee implements Serializable {
    public String firstName;
    private static final long serialVersionUID = 5462223600l;
    
    transient static String address = "DEL";
}
​
//执行“SerializaitonClass”和“DeserializationClass”,结果如下:
/*
Company Address: DEL
*/

如您所见,序列化过程中还保存了地址字段,因为它现在是最终的。

对未序列化的接口

public class Employee implements Serializable, variableConstant {
    public String firstName;
    private static final long serialVersionUID = 5462223600l;
    
    transient static String address = "DEL";
}
​
interface variableConstant  {
    public static final String education = "MCA";
}
​
//执行“SerializaitonClass”和“DeserializationClass”,结果如下:
/*
Company Address: DEL
Education: MCA
*/

在这里,您可以看到教育的值得以保存。 此值是接口的一部分。 但是由于这是恒定的,因此在序列化时将其保存。