Java序列化

2016/3/31 posted in  Java

序列化:一种将对象以一连串的字节描述的过程
反序列化:一种将这些字节重建成一个对象的过程

序列化的应用场景

  • 当你想把的内存中的对象保存到一个文件中或者数据库中时候(数据持久化);
  • 当你想用套接字在网络上传送对象的时候;
  • 当你想通过RMI传输对象的时候; Java RMI 支持存储于不同地址空间的程序级对象之间彼此进行通信,实现远程对象之间的无缝远程调用

实现序列化

将需要序列化的类实现Serializable接口就可以了,Serializable接口中没有任何方法,可以理解为一个标记,即表明这个类可以序列化.

序列化例子

FileOutputStream fos = new FileOutputStream("serialize.obj");  
ObjectOutputStream oos = new ObjectOutputStream(fos);  
Serialize serialize = new Serialize();  
oos.writeObject(serialize);  
oos.flush();  
oos.close();
fos.close();  

反序列化例子

FileInputStream fis = new FileInputStream("serialize.obj");  
ObjectInputStream ois = new ObjectInputStream(fis);  
serialize = (Serialize) ois.readObject();  
ois.close();  
fis.close();  

相关注意事项

  • 序列化时,只对对象的状态进行保存,而不管对象的方法;
  • 当一个父类实现序列化,子类自动实现序列化,不需要显式实现Serializable接口;
  • 当一个对象的实例变量引用其他对象,序列化该对象时也把引用对象进行序列化;
  • 并非所有的对象都可以序列化,至于为什么不可以,有很多原因了,比如:
    1. 安全方面的原因,比如一个对象拥有privatepublicfield,对于一个要传输的对象,比如写到文件,或者进行rmi传输 等等,在序列化进行传输的过程中,这个对象的private等域是不受保护的。
    2. 资源分配方面的原因,比如socketthread类,如果可以序列化,进行传输或者保存,也无法对他们进行重新的资源分配,而且也是没有必要这样实现。

序列化前和序列化后的对象的关系

序列化时深复制,反序列化还原后的对象地址与原来的不同。

不同的原因:
通过序列化操作,我们可以实现对任何可Serializable对象的”深度复制(deep copy)"——这意味着我们复制的是整个对象网,而不仅仅是基本对象及其引用。对于同一流的对象,他们的地址是相同,说明他们是同一个对象,但是与其他流的对象地址却不相同。也就说,只要将对象序列化到单一流中,就可以恢复出与我们写出时一样的对象网,而且只要在同一流中,对象都是同一个。

破坏单例模式

单例是要求一个JVM中只有一个类对象的, 而现在通过反序列化,一个新的对象克隆了出来.

package com.serialize;

import java.io.Serializable;

public class SerSingleton implements Serializable
{
    private static final long serialVersionUID = 1L;
    
    String name;
    
    private SerSingleton()
    {
        System.out.println("Singleton is create");
        name="SerSingleton";
    }
    
    private static SerSingleton instance = new SerSingleton();
    
    public static SerSingleton getInstance()
    {
        return instance;
    }
    
    public static void createString()
    {
        System.out.println("createString in Singleton");
    }
}

@Test
public void test() throws IOException, ClassNotFoundException
{
    SerSingleton s1= null;
    SerSingleton s = SerSingleton.getInstance();
    
    FileOutputStream fos = new FileOutputStream("SerSingleton.obj");
    ObjectOutputStream oos = new ObjectOutputStream(fos);
    oos.writeObject(s);
    oos.flush();
    oos.close();
    
    FileInputStream fis = new FileInputStream("SerSingleton.obj");
    ObjectInputStream ois = new ObjectInputStream(fis);
    s1 = (SerSingleton)ois.readObject();
    System.out.println(s==s1);
}
    
----------
private Object readResolve()  
{  
    return instance;  
}  

输出false

说明测试代码中的ss1指向了不同的实例,在反序列化后,生成多个对象实例。

加上第二部分代码:这样当JVM从内存中反序列化地"组装"一个新对象时,就会自动调用这个readResolve方法来返回我们指定好的对象了, 单例规则也就得到了保证.

序列化ID

序列化IDEclipse下提供了两种生成策略,一个是固定的1L,一个是随机生成一个不重复的long类型数据(实际上是使用JDK工具生成),在这里有一个建议,如果没有特殊需求,就是用默认的1L就可以,这样可以确保代码一致时反序列化成功。这也可能是造成序列化和反序列化失败的原因,因为不同的序列化id之间不能进行序列化和反序列化。

静态变量能否序列化

序列化会忽略静态变量,即序列化不保存静态变量的状态。静态成员属于类级别的,所以不能序列化。即 序列化的是对象的状态不是类的状态。这里的不能序列化的意思,是序列化信息中不包含这个静态成员域。transient后的变量也不能序列化。

transient小结

  1. 一旦变量被transient修饰,变量将不再是对象持久化的一部分,该变量内容在序列化后无法获得访问。

  2. transient关键字只能修饰变量,而不能修饰方法和类。注意,本地变量是不能被transient关键字修饰的。变量如果是用户自定义类变量,则该类需要实现Serializable接口。

  3. transient关键字修饰的变量不再能被序列化,一个静态变量不管是否被transient修饰,均不能被序列化。

注意:

Java对象序列化不仅保留一个对象的数据,而且递归保存对象引用的每个对象的数据

对象的序列化可以通过实现两种接口来实现,若实现的是Serializable接口,则所有的序列化将会自动进行,若实现的是Externalizable接口,则没有任何东西可以自动序列化,需要在writeExternal方法中进行手工指定所要序列化的变量,这与是否被transient修饰无关

总结

  1. 当父类继承Serializable接口时,所有子类都可以被序列化。
  2. 子类实现了Serializable接口,父类没有,父类中的属性不能被序列化(不报错,数据不会丢失)但是在子类中的属性仍能正确序列化
  3. 如果序列化的属性是对象,则这个对象也必须实现Serializable接口,否则会报错。
  4. 在反序列化时,如果对象的属性有修改或删减,则修改的部分属性会丢失,但不会报错。
  5. 在反序列化时,如果serialVersionUID被序列化,则反序列化时会失败
  6. 当一个对象的实例变量引用其他对象,序列化改对象时,也把引用对象进行序列化
  7. statictransient后的变量不能被序列化