Offer来了:Java面试核心知识点精讲(原理篇)
上QQ阅读APP看本书,新人免费读10天
设备和账号都新为新人

2.7 序列化

Java对象在JVM运行时被创建、更新和销毁,当JVM退出时,对象也会随之销毁,即这些对象的生命周期不会比JVM的生命周期更长。但在现实应用中,我们常常需要将对象及其状态在多个应用之间传递、共享,或者将对象及其状态持久化,在其他地方重新读取被保存的对象及其状态继续进行处理。这就需要通过将Java对象序列化来实现。

在使用Java序列化技术保存对象及其状态信息时,对象及其状态信息会被保存在一组字节数组中,在需要时再将这些字节数组反序列化为对象。注意,对象序列化保存的是对象的状态,即它的成员变量,因此类中的静态变量不会被序列化。

对象序列化除了用于持久化对象,在RPC(远程过程调用)或者网络传输中也经常被使用。

2.7.1 Java序列化API的使用

Java序列化API为处理对象序列化提供了一个标准机制,具体的Java系列化需要注意以下事项。

◎ 类要实现序列化功能,只需实现java.io.Serializable接口即可。

◎ 序列化和反序列化必须保持序列化的ID一致,一般使用private static final long serialVersionUID定义序列化ID。

◎ 序列化并不保存静态变量。

◎ 在需要序列化父类变量时,父类也需要实现Serializable接口。

◎ 使用Transient关键字可以阻止该变量被序列化,在被反序列化后,transient变量的值被设为对应类型的初始值,例如,int类型变量的值是0,对象类型变量的值是null。

具体的序列化实现代码如下:

import  java.io.Serializable;
//通过实现Serializable接口定义可序列化的Worker类
public  class  Wroker  implements  Serializable  {
    //定义序列化的ID
    private  static  final  long  serialVersionUID  =  123456789L;
    //name属性将被序列化
    private  String  name;
    //transient修饰的变量不会被序列化
    private  transient   int  salary;
    //静态变量属于类信息,不属于对象的状态,因此不会被序列化
    static  int  age  =100;
    public  String  getName()  {
      return  name;
    }
    public  void  setName(String  name)  {
      this.name  =  name;
    }
}

以上代码通过implements Serializable实现了一个序列化的类。注意,transient修饰的属性和static修饰的静态属性不会被序列化。

对象通过序列化后在网络上传输时,基于网络安全,我们可以在序列化前将一些敏感字段(用户名、密码、身份证号码)使用秘钥进行加密,在反序列化后再基于秘钥对数据进行解密。这样即使数据在网络中被劫持,由于缺少秘钥也无法对数据进行解析,这样可以在一定程度上保证序列化对象的数据安全。

2.7.2 序列化和反序列化

在Java生态中有很多优秀的序列化框架,比如arvo、protobuf、thrift、fastjson。我们也可以基于JDK原生的ObjectOutputStream和ObjectInputStream类实现对象进行序列化及反序列化,并调用其writeObject和readObject方法实现自定义序列化策略。具体的实现代码如下:

public  static  void  main(String[]  args)  throws  Exception  {
    //序列化数据到磁盘
    FileOutputStream  fos  =  new  FileOutputStream("worker.out");
    ObjectOutputStream  oos  =  new  ObjectOutputStream(fos);
    Wroker  testObject  =  new  Wroker();
    testObject.setName("alex");
    oos.writeObject(testObject);
    oos.flush();
    oos.close();
    //反序列化磁盘数据并解析数据状态
    FileInputStream  fis  =  new  FileInputStream("worker.out");
    ObjectInputStream  ois  =  new  ObjectInputStream(fis);
    Wroker  deTest  =  (Wroker)  ois.readObject();
    System.out.println(deTest.getName());
 }

以上代码通过文件流的方式将wroker对象的状态写入磁盘中,在需要使用的时候再以文件流的方式将其读取并反序列化成我们需要的对象及其状态数据。