Java序列化与反序列化入门理解

一、序列化与反序列化的概念以及使用场景

1、概念

    a)序列化:将对象转换成字节序列的过程;

    b)反序列化:将字节序列恢复成对象的过程。

2、使用场景
  1) 把对象的字节序列永久地保存到硬盘上,通常存放在一个文件中;
  2) 在网络上传送对象的字节序列。

二、结合例子说明

    进行序列化的对象,需要实现Serializable接口,否则将无法序列化。序列化采用ObjectOutputStream的writeObject方法实现;反序列化则使用ObjectInputStream的readObject将字节序列还原成对象。

首先创建一个User类,只有简单的几个属性,如下:

import java.io.Serializable;


public class User implements Serializable {
	private String name;
	private int age;
	private String address;
	
	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
	public int getAge() {
		return age;
	}
	public void setAge(int age) {
		this.age = age;
	}
	public String getAddress() {
		return address;
	}
	public void setAddress(String address) {
		this.address = address;
	}
	
	@Override
	public String toString() {
		//重写了toString,方便打印观察
		return "age:"+age+",name:"+name+",address:"+address;
	}
}

User对象已经实现了序列化接口。接着进行测试,如下:

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;

public class Test {

	/**
	 * 序列化
	 * @param filePath
	 * 		序列化要写入的文件路径
	 * @throws Exception
	 */
	public static void writeObject(String filePath)throws Exception{
		User u = new User();
		u.setAddress("福建省厦门市");
		u.setAge(18);
		u.setName("漫天的沙");
		
		ObjectOutputStream oos = null;
		try{
			oos = new ObjectOutputStream(new FileOutputStream(filePath));
			oos.writeObject(u);
			oos.flush();
		}finally{
			if(oos != null){
				oos.close();
			}
		}
	}
	
	/**
	 * 反序列化
	 * @param filePath
	 * 		序列化的文件
	 * @throws Exception
	 */
	public static void readObject(String filePath) throws Exception{
		ObjectInputStream ois = null;
		try{
			ois = new ObjectInputStream(new FileInputStream(filePath));
			User u = (User)ois.readObject();
			System.out.println(u);
		}finally{
			if(ois != null){
				ois.close();
			}
		}
	}
	
	public static void main(String[] args) throws Exception{
		String filePath = "f:/obj.out";
		writeObject(filePath);
		readObject(filePath);
	}
}

执行后打印如下:

证明反序列化成功。

三、扩展

1、serialVersionUID

实现了Serializable接口的对象,如果没有显性的设置serialVersionUID,系统会自动根据方法/属性等计算序列化ID,如果显性进行了设置,则直接使用该值。serialVersionUID有什么用呢?我们可以试着对User对象进行显性设置serialVersionUID=1L,然后再将之前序列化的文件进行反序列化,如下:

/** 为用户对象显性设置序列化id */
private static final long serialVersionUID = 1L;

重新运行程序,报错如下:

Exception in thread "main" java.io.InvalidClassException: User; local class incompatible: stream classdesc serialVersionUID = -5216935088914625952, local class serialVersionUID = 1
	at java.io.ObjectStreamClass.initNonProxy(ObjectStreamClass.java:562)
	at java.io.ObjectInputStream.readNonProxyDesc(ObjectInputStream.java:1583)
	at java.io.ObjectInputStream.readClassDesc(ObjectInputStream.java:1496)
	at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:1732)
	at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1329)
	at java.io.ObjectInputStream.readObject(ObjectInputStream.java:351)
	at Test.readObject(Test.java:42)
	at Test.main(Test.java:54)

意思即序列化文件的序列化ID与要反序列化成的对象序列化ID不兼容,因此反序列化失败。所以,如果序列化对象后续可能会增减字段或者修改,为了保证兼容,需要显性的设置序列化ID,避免修改之后原先的序列化不能成功的反序列化产生问题。

2、transient字段使用

    如果字段不需要进行序列化,可以在修饰符后添加transient的声明,这样在序列化的时候属性将不参与序列化,例如上面的User对象name不参与序列化,则声明为

/** transient在对象初始化时可以让该字段不参与序列化 */
private transient String name;

重新运行程序,如下:

age:18,name:null,address:福建省厦门市

3、静态变量序列化

正常情况下,静态变量也可以正常的序列化。但是如果中间静态变量进行调整呢,序列化的对象中该静态变量是否会产生变化?为了验证效果,在上面的User新增一个静态变量,同时为了方便观察,重写toString方法,如下:

/**User对象新增的静态变量*/
public static int a = 35555;

@Override
	public String toString() {
		//重写了toString,方便打印观察
		return "age:"+age+",name:"+name+",address:"+address+",a:"+a;
	}

对main方法调整了,如下:

public static void main(String[] args) throws Exception{
		String filePath = "f:/obj.out";
		writeObject(filePath);
		//修改User静态变量a的值
		User.a = 10;
		readObject(filePath);
	}

重新运行,结果如下:

age:18,name:null,address:福建省厦门市,a:10

说明静态变量的值变了。分析原因,因为序列化保存的是对象的状态,静态变量属于类的状态,因此序列化并不保存静态变量。


下一篇:java序列化与反序列化进阶(一)

你可能感兴趣的:(Java序列化与反序列化入门理解)