一 名词定义
序列化:将对象转化成二进制序列
反序列化:将序列化的二进制序列转化为对象
serialVersionUID:唯一的版本序列ID
二 序列化方式
1. 实现序列化接口Serializable的方式
eg.
package com.shop.domain;
import java.util.Date;
public class Article implements java.io.Serializable {
private static final long serialVersionUID = 1L;
private Integer id;
private String title; //文章标题
private String content; // 文章内容
private String faceIcon;//表情图标
private Date postTime; //文章发表的时间
private String ipAddr; //用户的ip
private User author; //回复的用户
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
public String getFaceIcon() {
return faceIcon;
}
public void setFaceIcon(String faceIcon) {
this.faceIcon = faceIcon;
}
public Date getPostTime() {
return postTime;
}
public void setPostTime(Date postTime) {
this.postTime = postTime;
}
public User getAuthor() {
return author;
}
public void setAuthor(User author) {
this.author = author;
}
public String getIpAddr() {
return ipAddr;
}
public void setIpAddr(String ipAddr) {
this.ipAddr = ipAddr;
}
}
2.实现序列化的第二种方式为实现接口Externalizable
eg.
* @see java.io.ObjectInput
* @see java.io.Serializable
* @since JDK1.1
*/
public interface Externalizable extends java.io.Serializable {
/**
* The object implements the writeExternal method to save its contents
* by calling the methods of DataOutput for its primitive values or
首先,我们在序列化对象的时候,由于这个类实现了Externalizable 接口,在writeExternal()方法里定义了哪些属性可以序列化,
package com.xiaohao.test;
import java.io.Externalizable;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectInputStream;
import java.io.ObjectOutput;i
mport java.io.ObjectOutputStream;
import java.text.SimpleDateFormat;
import java.util.Date;
/**
* 测试实体类
*/
class Person implements Externalizable{
private static final long serialVersionUID = 1L;
String userName; String password; String age;
public Person(String userName, String password, String age) {
super();
this.userName = userName;
this.password = password;
this.age = age;
}
public Person() {
super();
}
public String getAge() {
return age;
}
public void setAge(String age) {
this.age = age;
}
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
/**
* 序列化操作的扩展类
*/
@Override
public void writeExternal(ObjectOutput out) throws IOException {
//增加一个新的对象
Date date=new Date();
out.writeObject(userName);
out.writeObject(password);
out.writeObject(date);
}
/**
* 反序列化的扩展类
*/
@Override
public void readExternal(ObjectInput in) throws IOException,ClassNotFoundException {
//注意这里的接受顺序是有限制的哦,否则的话会出错的
// 例如上面先write的是A对象的话,那么下面先接受的也一定是A对象...
userName=(String) in.readObject();
password=(String) in.readObject();
SimpleDateFormat sdf=new SimpleDateFormat("yyyy-MM-dd");
Date date=(Date)in.readObject();
System.out.println("反序列化后的日期为:"+sdf.format(date));
}
@Override
public String toString() {
//注意这里的年龄是不会被序列化的,所以在反序列化的时候是读取不到数据的
return "用户名:"+userName+"密 码:"+password+"年龄:"+age;
}
}
/**
* 序列化和反序列化的相关操作类
*/
class Operate{
/**
* 序列化方法
* @throws IOException
* @throws FileNotFoundException
*/
public void serializable(Person person) throws FileNotFoundException, IOException{
ObjectOutputStream outputStream=new ObjectOutputStream(new FileOutputStream("a.txt"));
outputStream.writeObject(person);
}
/**
* 反序列化的方法
* @throws IOException
* @throws FileNotFoundException
* @throws ClassNotFoundException
*/
public Person deSerializable() throws FileNotFoundException, IOException, ClassNotFoundException{
ObjectInputStream ois=new ObjectInputStream(new FileInputStream("a.txt"));
return (Person) ois.readObject();
}
}
/**
* 测试实体主类
*/
public class Test{
public static void main(String[] args) throws FileNotFoundException, IOException, ClassNotFoundException {
Operate operate=new Operate();
Person person=new Person("小浩","123456","20");
System.out.println("为序列化之前的相关数据如下:\n"+person.toString());
operate.serializable(person);
Person newPerson=operate.deSerializable();
System.out.println("-------------------------------------------------------");
System.out.println("序列化之后的相关数据如下:\n"+newPerson.toString()); }
}
首先,我们在序列化UserInfo对象的时候,由于这个类实现了Externalizable 接口,在writeExternal()方法里定义了
哪些属性可以序列化,哪些不可以序列化,
所以,对象在经过这里就把规定能被序列化的序列化保存文件,不能序列化的不处理,然后在反序列的时候自动
调用readExternal()方法,根据序列顺序挨个读取进行反序列,并自动封装成对象返回,然后在测试类接收,
就完成了反序列化。
注意:
1.java中的序列化时transient变量(这个关键字的作用就是告知JAVA我不可以被序列化)和静态变量不会被序列化
2.如果你先序列化对象A后序列化B,那么在反序列化的时候一定记着JAVA规定先读到的对象是先被序列化的对象,不要先接收对象B,
那样会报错.尤其在使用上面的Externalizable的时候一定要注意读取的先后顺序。
三 serialVersionUID作用
serialVersionUID 是在序列化和反序列化的时候做比对使用;之又当序列化的版本号和反序列化的版本号匹配反序列化才会成功。否则咋反序列化的时候会报InvalidClassException异常。
显示指定:在需要序列化的类中通过private static final long serialVersionUID = 123456789L;
方式手动指定 。在序列化的时候会使用指定的serialVersionUID,在序列化完成之后,即使修改过
序列化类,在原序列化对象反序列化的时候也不会nvalidClassException。
隐式生成:如过序列化的类没有指定serialVersionUID Java序列化机制会根据编译的Class自动生成一个serialVersionUID。
当序列化完成之后,修改过源序列化类之后,在原序列化对象反序列化的时候会报nvalidClassException。
如下图:
拓展一:
静态变量的序列化和反序列化:
demo:
public class Test implements Serializable { private static final long serialVersionUID = 1L; public static int staticVar = 5; public static void main(String[] args) { try { //初始时staticVar为5 ObjectOutputStream out = new ObjectOutputStream( new FileOutputStream("result.obj")); out.writeObject(new Test()); out.close(); //序列化后修改为10 Test.staticVar = 10; ObjectInputStream oin = new ObjectInputStream(new FileInputStream( "result.obj")); Test t = (Test) oin.readObject(); oin.close(); //再读取,通过t.staticVar打印新的值 System.out.println(t.staticVar); } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } catch (ClassNotFoundException e) { e.printStackTrace(); } } }
输出的结果是10. 序列化是对象的状态,静态变量是属于类的状态。所以反序列化获取到的对象里面没有静态属性,在序列化之后给静态属性重新赋值。
拓展二:Transient变量
情境1:一个子类实现了 Serializable 接口,它的父类都没有实现 Serializable 接口,序列化该子类对象,然后反序列化后输出父类定义的某变量的数值,该变量数值与序列化时的数值不同。
解决:因为父类没有实现Serializable接口,所以父类不能被序列化。在反序列化的时候会先调用父类默认的无参数构造函数,
如果父类的无参数构造函数中没有手动给变量赋值,在反序列化的时候父类对象的属性都会给根据数据类型给默认值。
所以反序列化后输出父类定义的某变量的数值,该变量数值与序列化时的数值不同。
Transient 关键字的作用是控制变量的序列化,在变量声明前加上该关键字,可以阻止该变量被序列化到文件中,在被反序列化后,transient 变量的值被设为初始值。
特性使用案例
我们熟悉使用 Transient 关键字可以使得字段不被序列化,那么还有别的方法吗?
根据父类对象序列化的规则,我们可以将不需要被序列化的字段抽取出来放到父类中,子类实现 Serializable 接口,父类不实现,根据父类序列化规则,父类的字段数据将不被序列化,
形成类图如图 2
图 2. 案例程序类图
上图中可以看出,attr1、attr2、attr3、attr5 都不会被序列化,放在父类中的好处在于当有另外一个 Child 类时,attr1、attr2、attr3 依然不会被序列化,不用重复抒写 transient,代码简洁