Java序列化

1、序列化概述

  对象序列化可以使对象保存在磁盘或发送到网络中,为了让类支持序列化,该类应该实现Serializable或Externalizable接口。Java中很多类已经实现了Serializable,Serializable是一个标记接口,实现该接口无需实现它的方法,建议每个JavaBean类都实现Serializable。

  JavaBean是遵循一定编程原则的Java类的统称,由属性、方法和事件3部分组成,使用它可以实现代码的可重用性,保持类功能的向后兼容。JavaBean遵循的约定有:

  ①、所有的JavaBean必须放在一个包(Package)中。
  ②、JavaBean应该生成public class类,文件名称应该与类名称一致。
  ③、所有属性必须封装为private,设置、获取属性值应该通过一组方法:setter()、getter()、geXxx()、setXxx()、isXxx(),这样当修改该类后对外提供的接口无需改变。
  ④、Java Bean 类必须有不带参数的构造函数,这个构造器通过调用各个属性的设置方法来设置属性的默认值。

  我们可以使用lombok这个小工具来简化代码编写,,lombok是一个可以通过简单的注解的形式来自动生成代码的工具,比如如下所示,对一个方法的参数添加@NonNull注解后,在编译的时候会为该参数添加为空的判断。再比如下所示,对一个类添加注解后就自动为该类添加了getter和setter方法(编译源码的时候自动生成这些方法)。

   

2、序列化流程

  序列化一个对象的步骤:
  ①、该对象所属类实现Serializable接口。
  ②、创建一个ObjectOutputStream处理流,该处理流建立在一个节点流基础上。
  ③、调用ObjectOutputStream的writeObject()方法保存可序列化的对象。

  反序列化一个对象的步骤:
  ①、提供该对象所属类的class文件,否则会引发ClassNotFoundException异常。
  ②、创建一个ObjectInputStream处理流,该处理流建立在一个节点流基础上。
  ③、调用ObjectInputStream的readObject()方法保存可序列化的对象

//Person.java
public class Person implements java.io.Serializable
{
	private String name;
	private int age;
	public Person(){}
	public Person(String name , int age)
	{
		System.out.println("有参数的构造器");
		this.name = name;
		this.age = age;
	}
	// 省略name与age的setter和getter方法
	public void setName(String name)
	{
		this.name = name;
	}
	public String getName()
	{
		return this.name;
	}
	public void setAge(int age)
	{
		this.age = age;
	}
	public int getAge()
	{
		return this.age;
	}
}


//WriteObject.java
import java.io.*;

public class WriteObject
{
	public static void main(String[] args)
	{
		try(
			ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("object.txt"));
			ObjectInputStream ois = new ObjectInputStream(new FileInputStream("object.txt")))
		{
			Person per = new Person("孙悟空", 500);
			oos.writeObject(per);
			
			Person p = (Person)ois.readObject();
			int age = p.getAge();
		}
		catch (Exception ex)
		{
			ex.printStackTrace();
		}
	}
}

3、序列化需要注意的地方

  反序列化机制不会通过构造器来初始化Java对象。
  当调用writeObject()方法序列化一个对象后,改变该对象的成员变量的值再次调用writeObject()方法序列化该对象只会输出序列化编号,不会再次序列化该对象。
  使用transient声明的成员变量(又称瞬态实例变量)不会被序列化,在反序列化的时候会得到其为0或null。
  使用序列化机制写入了多个Java对象后,反序列化的时候应该与写入顺序一致。
  可序列化类的父类也应该是可序列化的,如果父类是不可序列化的,但是带有无参数的构造器,那么父类中成员变量的值不会被序列化。

  序列化对象的实例变量也应该是可序列化的,而方法、类变量不会被实例化。

4、自定义序列化

  可以重写以下方法来自定义序列化和反序列化,其中writeObject()为重写序列化方法,readObject()可以重写反序列化方法,readObjectNoData()重写序列化流不完整时(接收方使用的反序列化类的版本不同于发送方、接收方版本扩展的类不是发送方版本扩展的类、序列化流被篡改)的方法,writeReplace()方法中可以返回指定对象来替换序列化的对象,readResolve()中返回指定的对象会替换掉反序列化的对象:

  private void writeObject(java.io.ObjectOutputStream out)throws IOException;
  private void readObject(java.io.ObjectInputStream in)throws IOException, ClassNotFoundException;
  private void readObjectNoData()throws ObjectStreamException;
  ANY-ACCESS-MODIFIER Object writeReplace()throws ObjectStreamException;
  ANY-ACCESS-MODIFIER Object readResolve()throws ObjectStreamException;

  使用writeObject()和readObject():

import java.io.*;
public class Person
	implements java.io.Serializable
{
	private String name;
	private int age;
	private void writeObject(java.io.ObjectOutputStream out)
		throws IOException
	{
		// 将name实例变量的值反转后写入二进制流
		out.writeObject(new StringBuffer(name).reverse());
		// 将int类型的age写入二进制流
		out.writeInt(age);
	}
	private void readObject(java.io.ObjectInputStream in)
		throws IOException, ClassNotFoundException
	{
		// 将读取的字符串反转后赋给name实例变量
		this.name = ((StringBuffer)in.readObject()).reverse().toString();
		// 将读取的int型赋给age实例变量
		this.age = in.readInt();
	}
}

  使用writeReplace:

public class Person implements java.io.Serializable
{
	private String name;
	private int age;
	//	重写writeReplace方法,程序在序列化该对象之前,先调用该方法
	private Object writeReplace()throws ObjectStreamException
	{
		ArrayList<Object> list = new ArrayList<>();
		list.add(name);
		list.add(age);
		return list;
	}
}

5、Externalizable

  类实现Serializable接口后就可以序列化该类的对象,可以重写其方法来自定义序列化。类实现Externalizable接口的话也可以实现其对象的序列化,但必须实现该接口,即必须重写readExternal()和writeExternal()方法,因为这两个方法是空方法:

import java.io.*;

public class Person
	implements java.io.Externalizable
{
	private String name;
	private int age;
	public void writeExternal(java.io.ObjectOutput out)
		throws IOException
	{
		out.writeObject(new StringBuffer(name).reverse());
		out.writeInt(age);
	}
	public void readExternal(java.io.ObjectInput in)
		throws IOException, ClassNotFoundException
	{
		this.name = ((StringBuffer)in.readObject()).reverse().toString();
		this.age = in.readInt();
	}
}

6、序列化版本

  前面说过,反序列化时应该提供该对象的class文件,如果反序列化之前该类已经修改(即与序列化的时候不同了)的话,反序列化可能会失败。应该通过指定private static final long serialVersionUid = xxx;来指示该类与序列化时的类属于同一版本:如果仅是修改了该类的方法、类变量、瞬态实例变量,则反序列化不会受影响,serialVersionUid值无需修改,指示两个类为同一版本。如果修改了类的实例变量,比如将实例变量的类型由int改为String,则反序列化会失败,此时应该修改serialVersionUid为新的值,指示两个类版本已不同。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值