java类和对象:类的序列化

我们两清 提交于 2020-03-01 10:20:03

1、序列化介绍

Java 串行化技术可以使你将一个对象的状态写入一个Byte流里,并且可以从其它地方把该Byte 流里的数据读出来,重新构造一个相同的对象。这种机制允许你将对象通过网络进行传播,并可以随时把对象持久化到数据库、文件等系统里。Java的串行化机制是RMI、EJB等技术的技术基础。

用途:利用对象的串行化实现保存应用程序的当前工作状态,下次再启动的时候将自动地恢复到上次执行的状态。

序列化就是一种用来处理对象流的机制,所谓对象流也就是将对象的内容进行流化。可以对流化后的对象进行读写操作,也可将流化后的对象传输于网络之间。

序列化是为了解决在对对象流进行读写操作时所引发的问题。

2、串行化的特点

  (1)如果某个类能够被串行化,其子类也可以被串行化。如果该类有父类,则分两种情况来考虑,如果该父类已经实现了可串行化接口。则其父类的相应字段及属性的处理和该类相同;如果该类的父类没有实现可串行化接口,则该类的父类所有的字段属性将不会串行化。

  (2)声明为static和transient类型的成员数据不能被串行化。因为static代表类的状态, transient代表对象的临时数据;

  (3)相关的类和接口:在java.io包中提供的涉及对象的串行化的类与接口有ObjectOutput接口、ObjectOutputStream类、ObjectInput接口、ObjectInputStream类。

  • ObjectOutput接口:它继承DataOutput接口并且支持对象的串行化,其内的writeObject()方法实现存储一个对象。
  • ObjectInput接口:它继承DataInput接口并且支持对象的串行化,其内的readObject()方法实现读取一个对象。
  • ObjectOutputStream类:它继承OutputStream类并且实现ObjectOutput接口。利用该类来实现将对象存储(调用ObjectOutput接口中的writeObject()方法)。
  • ObjectInputStream类:它继承InputStream类并且实现ObjectInput接口。利用该类来实现读取一个对象(调用ObjectInput接口中的readObject()方法)。

   对于父类的处理,如果父类没有实现串行化接口,则其必须有默认的构造函数(即没有参数的构造函数)。否则编译的时候就会报错。在反串行化的时候,默认构造函数会被调用。但是若把父类标记为可以串行化,则在反串行化的时候,其默认构造函数不会被调用。这是因为Java 对串行化的对象进行反串行化的时候,直接从流里获取其对象数据来生成一个对象实例,而不是通过其构造函数来完成。

3、序列化的实现

基本方法:将需要被序列化的类实现Serializable接口,然后使用一个输出流(如:FileOutputStream)来构造一个ObjectOutputStream(对象流)对象,接着,使用ObjectOutputStream对象的writeObject(Object obj)方法就可以将参数为obj的对象写出(即保存其状态),要恢复的话则用输入流。

serialVersionUID的作用:简单来说,Java的序列化机制是通过在运行时判断类的serialVersionUID来验证版本一致性的。在进行反序列化时,JVM会把传来的字节流中的serialVersionUID与本地相应实体(类)的serialVersionUID进行比较,如果相同就认为是一致的,可以进行反序列化,否则就会出现序列化版本不一致的异常。(InvalidCastException)

serialVersionUID有两种显示的生成方式:       

  • 一个是默认的1L,比如:private static final long serialVersionUID = 1L;       
  • 一个是根据类名、接口名、成员方法及属性等来生成一个64位的哈希字段,比如:private static final long serialVersionUID = xxxxL;

4、类的序列化读写实现

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import junit.framework.TestCase;
/**
 * 使用Java序列化把对象存储到文件中,再从文件中读出来 注意读取的时候,读取数据的顺序一定要和存放数据的顺序保持一致
 * 
 * @author Champion Wong
 * 
 */
public class Test08 extends TestCase {
    public void test() {
        // 创建一个User对象
        User user = new User();
        user.setId(1);
        user.setName("Mr XP.Wang");
        // 创建一个List对象
        List<String> list = new ArrayList<String>();
        list.add("My name");
        list.add(" is");
        list.add(" Mr XP.Wang");
        try {
            ObjectOutputStream os = new ObjectOutputStream(
                    new FileOutputStream("C:/wxp.txt"));
            os.writeObject(user);// 将User对象写进文件
            os.writeObject(list);// 将List列表写进文件
            os.close();
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
        try {
            ObjectInputStream is = new ObjectInputStream(new FileInputStream(
                    "C:/wxp.txt"));
            User temp = (User) is.readObject();// 从流中读取User的数据
            System.out.println(temp.getId());
            System.out.println(temp.getName());
            List tempList = (List) is.readObject();// 从流中读取List的数据
            for (Iterator iterator = tempList.iterator(); iterator.hasNext();) {
                System.out.print(iterator.next());
            }
            is.close();
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
}
class User implements Serializable {
    private int id;
    private String name;
    public int getId() {
        return id;
    }
    public void setId(int id) {
        this.id = id;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
}

 5、类的序列化应用探讨

用途利用对象的串行化实现保存应用程序的当前工作状态,下次再启动的时候将自动地恢复到上次执行的状态。例如:游戏状态的保存。

如何保持向上兼容性

向上兼容性是指老的版本能够读取新的版本序列化的数据流。常常出现在我们的服务器的数据更新了,仍然希望老的客户端能够支持反序列化新的数据流,直到其更新到新的版本。可以说,这是半自动的事情。

跟一般的讲,因为在java中serialVersionUID是唯一控制着能否反序列化成功的标志,只要这个值不一样,就无法反序列化成功。但只要这个值相同,无论如何都将反序列化,在这个过程中,对于向上兼容性,新数据流中的多余的内容将会被忽略;对于向下兼容性而言,旧的数据流中所包含的所有内容都将会被恢复,新版本的类中没有涉及到的部分将保持默认值。利用这一特性,可以说,只要我们认为的保持serialVersionUID不变,向上兼容性是自动实现的。

当然,一但我们将新版本中的老的内容拿掉,情况就不同了,即使UID保持不变,会引发异常。正是因为这一点,我们要牢记一个类一旦实现了序列化又要保持向上下兼容性,就不可以随随便便的修改了!

如何保持向下兼容性

一如上文所指出的,你会想当然的认为只要保持serialVersionUID不变,向下兼容性是自动实现的。但实际上,向下兼容要复杂一些。这是因为,我们必须要对那些没有初始化的字段负责。要保证它们能被使用。

private void readObject(java.io.ObjectInputStream in) throws IOException, ClassNotFoundException{ 
 in.defaultReadObject();//先反序列化对象 
 if(ver=5552){
  //以前的版本5552 
  …初始化其他字段 
 }else if(ver=5550){
  //以前的版本5550 
  …初始化其他字段 
 }else{
  //太老的版本不支持 
  throw new InvalidClassException(); 
 } 
}

细心的读者会注意到要保证in.defaultReadObject();能够顺利执行,就必须要求serialVersionUID保持一致,所以这里的ver不能够利用serialVersionUID了。这里的ver是一个我们预先安插好的final long ver=xxxx;并且它不能够被transient修饰。

所以保持向下的兼容性至少有三点要求:

1.serialVersionUID保持一致

2.预先安插好我们自己的版本识别标志的final long ver=xxxx;

3.保证初始化所有的域

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!