一、简介
序列化:把对象存储为二进制格式(字节序列)的过程(我们看不懂,但是计算机能够识别这些字节序列)。
反序列化:把二进制格式(字节序列)还原为相应的对象的过程。
二、序列化的应用场景
实际工作中用到序列化的场景主要有两个:
【a】对象持久化: 对象持久化操作的时候,可以保存在文件也可以保存在数据库中,常见的都是保存在数据库中。
【b】网络传输: 当对象需要在网络上传输的时候,也是需要序列化对象的。
三、序列化与反序列化的实现
在Java中实现序列化与反序列化主要使用到的类以及方法有:
【a】需要序列化的类必须实现Serializable接口。
【b】使用ObjectOutputStream的writeObject()方法将对象序列化为二进制文件。
【c】使用ObjectInputStream的readObject()方法将二进制文件反序列化为相应的对象。
下面,通过具体的示例讲解序列化与反序列化的实现:
(1)、定义一个实体类(这里假设为会员实体类Member.java),实现Serializable接口
/**
* @Description: 会员实体类
* @Author: weishihuai
* @Date: 2018/11/4 19:41
* 说明: 实现序列化接口Serializable
*/
public class Member implements Serializable {
/**
* 会员ID
*/
private String memberId;
/**
* 会员姓名
*/
private String name;
/**
* 体重(注意: 这里使用了transient关键字修饰)
*/
private transient Double weight;
/**
* 身高(注意: 静态属性、类属性)
*/
private static Double height;
public Member(String memberId, String name, Double weight) {
this.memberId = memberId;
this.name = name;
this.weight = weight;
}
public String getMemberId() {
return memberId;
}
public void setMemberId(String memberId) {
this.memberId = memberId;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Double getWeight() {
return weight;
}
public void setWeight(Double weight) {
this.weight = weight;
}
public static Double getHeight() {
return height;
}
public static void setHeight(Double height) {
Member.height = height;
}
@Override
public String toString() {
return "Member{" +
"memberId='" + memberId + '\'' +
", name='" + name + '\'' +
", weight=" + weight +
'}';
}
}
(2)、编写序列化与反序列化操作的类SerializableDemo.java:
/**
* @Description: 序列化与反序列化
* @Author: weishihuai
* @Date: 2018/11/4 19:45
* 说明:
* 序列化: 输出流 ObjectOutputStream
* 反序列化: 输入流 ObjectInputStream
* <p>
* 1. 先序列化再反序列化
* 2. 不是所有的对象都可以序列化与反序列化
* 3. 不是所有的属性都需要序列化,不需要序列化的属性使用transient关键字修饰
*/
public class SerializableDemo {
public static void main(String[] args) {
//将对象序列化到d:/aaa/member.txt中
writeObjectToBinary("d:/aaa/member.txt");
//将d:/aaa/member.txt中的二进制文件还原为实体对象
Member member = readObjectFromBinary("d:/aaa/member.txt");
System.out.println(member);
}
/**
* 序列化操作
*
* @param destPath 序列化后存放二进制文件的目标路径
*/
public static void writeObjectToBinary(String destPath) {
//1. 创建需要序列化的对象
Member member = new Member(UUID.randomUUID().toString(), "zhangsan", 60.5D);
ObjectOutputStream oos = null;
try {
//2. 建立与输出文件的路径的联系
oos = new ObjectOutputStream(new BufferedOutputStream(new FileOutputStream(new File(destPath))));
//3. 使用ObjectOutPutStream的writeObject()方法将对象序列化到文件
oos.writeObject(member);
} catch (IOException e) {
e.printStackTrace();
} finally {
//4. 关闭流
if (null != oos) {
try {
oos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
/**
* 反序列化操作
*
* @param sourcePath 反序列化时二进制文件的源路径
* @return 反序列化后的对象
*/
public static Member readObjectFromBinary(String sourcePath) {
//1. 建立与源二进制文件的联系
File file = new File(sourcePath);
ObjectInputStream ois = null;
try {
ois = new ObjectInputStream(new BufferedInputStream(new FileInputStream(file)));
// 2. 使用ObjectInputStream的readObject()方法将二进制文件还原为实体对象
Object object = ois.readObject();
// 3. 判断是否为Member类型
if (object instanceof Member) {
return (Member) object;
}
} catch (IOException | ClassNotFoundException e) {
e.printStackTrace();
} finally {
// 4. 关闭流
if (null != ois) {
try {
ois.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
return null;
}
}
(3)、运行程序:结果如下图
以上是Member对象序列化后变成的字节序列(二进制格式),计算机能够识别这些二进制格式的文件,我们并看不懂文件。
以上是将二进制格式的文件反序列化为Member对象之后输出的属性,根据输出可以看到:
使用关键字transient修饰的属性不会进行序列化操作,反序列化后这些属性为null或者0。
(4)、测试静态属性height是否能够序列化
public class SerializableDemo {
public static void main(String[] args) {
//将对象序列化到d:/aaa/member.txt中
writeObjectToBinary("d:/aaa/member.txt");
}
}
【a】首先执行序列化操作,这时候我们序列化进去的height属性的值为180.5
private static Double height = 180.5D;
【b】然后我们修改height属性的值为165.5,
private static Double height = 165.5D;
【c】这时候我们进行反序列化操作
public class SerializableDemo {
public static void main(String[] args) {
//将d:/aaa/member.txt中的二进制文件还原为实体对象
Member member = readObjectFromBinary("d:/aaa/member.txt");
System.out.println(member);
}
}
【d】控制台输出:
由图可见,反序列化之后的member对象中height静态属性的值为165.5,并不是我们序列化时保存的值180.5,由此可以证明静态属性不进行序列化。
四、注意点
【a】实体类必须实现序列化接口Serializable,否则会报错。
我们先去掉Member实体类的implements Seriablizable,执行序列化操作,发现控制台报错。
当你看到这个报错信息的时候,就应该知道是少了实现Seriablizable接口。
【b】序列化ID (serialVersionUID)的问题
(1) 首先我们执行序列化操作
public class SerializableDemo {
public static void main(String[] args) {
//将对象序列化到d:/aaa/member.txt中
writeObjectToBinary("d:/aaa/member.txt");
}
}
(2) 然后我们去修改Member.java,给它加多一个属性mobile:
public class Member implements Serializable {
/**
* 会员ID
*/
private String memberId;
/**
* 会员姓名
*/
private String name;
/**
* 体重(注意: 这里使用了transient关键字修饰)
*/
private transient Double weight;
/**
* 身高(注意: 静态属性、类属性)
*/
private static Double height = 165.5D;
/**
* 手机号码
*/
private String mobile;
//setter getter toString
}
(3) 然后执行反序列化操作
public class SerializableDemo {
public static void main(String[] args) {
//将d:/aaa/member.txt中的二进制文件还原为实体对象
Member member = readObjectFromBinary("d:/aaa/member.txt");
System.out.println(member);
}
}
(4) 运行结果
由图可见,反序列化的时候报错了。原因就是因为序列化与反序列化时的serialVersionUID不一致导致。
(5) 解决方法
给实体类Member.java指定serialVersionUID,这样序列化与反序列化的serialVersionUID一致,即使序列化后修改了实体类,反序列化也不会报错。可以简单的指定为1,当然也可以使用随机数。
private static final long serialVersionUID = 1L;
public class Member implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 会员ID
*/
private String memberId;
/**
* 会员姓名
*/
private String name;
/**
* 体重(注意: 这里使用了transient关键字修饰)
*/
private transient Double weight;
/**
* 身高(注意: 静态属性、类属性)
*/
private static Double height = 165.5D;
/**
* 手机号码
*/
private String mobile;
}
(6)重复以上步骤,发现序列化后,然后修改实体类,然后反序列化没有报错了。
五、总结
以上是关于Java中序列化以及反序列化的方法,以及通过示例讲解了如何实现序列化反序列化,还有谈了一些实际工作中需要注意的一些问题。本文是作者在复习IO操作ObjectInputStream和ObjectOutputStream的时候,顺便研究了下序列化以及反序列化操作,本文只是作者的一些见解与总结,仅供大家学习参考,希望能对大家有所帮助!