序列化和反序列化
序列化和反序列化
1、什么是序列化
序列化:把Java对象转换成字节序列
反序列化:把字节序列恢复成原先的Java对象
2、序列化的作用
- 持久化Java对象(如将Java对象保存到文件中),主要目的是通过网络传输对象,或是将对象存储到文件系统、内存等中
- 同时也在一定程度上弥补了平台化的一些差异,转换后的字节流可以在其他平台上反序列化来恢复对象
3、序列化的实现
应用场景:
- 进行网络传输前需要先进行序列化
- 将Java对象存储到文件(或数据库等)时进行序列化,读取文件时进行反序列化
需要序列化的类需要实现Serializable接口
示例代码:
public class Student implements Serializable {
private int age;
private String name;
//get,set,构造器 等省略
}
public class Test {
public static void main(String[] args) throws IOException, ClassNotFoundException {
Student student = new Student(18,"zhangSan");
ObjectOutputStream objectOutputStream = new ObjectOutputStream(
new FileOutputStream("student.txt"));
objectOutputStream.writeObject(student);
System.out.println("序列化成功");
objectOutputStream.close();
System.out.println("关闭资源");
ObjectInputStream objectInputStream = new ObjectInputStream(
new FileInputStream("student.txt"));
Student student2 = (Student)objectInputStream.readObject();
System.out.println("反序列化成功");
System.out.println(student2);
objectInputStream.close();
System.out.println("关闭资源");
System.out.println(student == student2);
}
}
/* 输出结果:
序列化成功
关闭资源
反序列化成功
Student{age=18, name='zhangSan'}
关闭资源
false : 由此可见,序列化生成了一个新的对象
*/
4、Serializable接口

Serializable接口从jdk1.1开始就有了,他只是一个空接口,仅仅起一个标记的作用。但是如果需要序列化的类没有实现Serializable接口的话,在进行序列化时就会抛出NotSerializableException异常。
5、序列化中需要注意的几个点
5.1、 serialVersionUID,序列化版本号
-
作用:作为序列化前后的标识符,在反序列化时,只有当字节流中的序列化版本号和被序列化的类的版本号一致是才能进行反序列化;当两者不一致时,就会抛出
InvalidClassException -
测试代码:
public class Student implements Serializable {
private int age;
private String name;
private String sex; //序列化之后在Student类中添加一个sex属性,其他都不改变,进行测试
}

-
很明显的可以看出,有两个序列化版本号,因为两个版本号不一致所以无法进行反序列化;但是我们并没有去定义这个
serialVersionUID,那么他是哪里来的呢? -
这个问题有点类似于对象的无参构造器,当没有显示的声明构造器是,编译器会给对象声明一个隐式的无参构造器,在对象实例化时调用。这个
serialVersionUID也是一样的,当没有为他显示的定义时,编译器会自动声明一个。但一般还是建议人为的定义一个显示的serialVersionUID
5.2、transient关键字
当我们在序列化时(如将对象写入.txt文件中),如果有一个属性是比较隐秘的(如:密码),我们不希望他进行序列化,那么这个时候我们就需要用到transient关键字了。
测试代码:
public class Student implements Serializable {
private int age;
private String name;
private transient String sex;//我们将sex属性用transient进行修饰
}
public static void main(String[] args) throws IOException, ClassNotFoundException {
Student student = new Student(18,"zhangSan","男");
ObjectOutputStream objectOutputStream = new ObjectOutputStream(
new FileOutputStream("student.txt"));
objectOutputStream.writeObject(student);
System.out.println("序列化成功");
objectOutputStream.close();
System.out.println("关闭资源");
ObjectInputStream objectInputStream = new ObjectInputStream(
new FileInputStream("student.txt"));
Student student2 = (Student)objectInputStream.readObject();
System.out.println("反序列化成功");
System.out.println(student2);
objectInputStream.close();
System.out.println("关闭资源");
}
/* 得到结果
序列化成功
关闭资源
反序列化成功
Student{age=18, name='zhangSan', sex='null'}
关闭资源
*/
可以看到,被transient修饰的sex属性反序列化得到的是null;
此外,被static关键字修饰的属性也是无法被序列化,反序列化的;这点很好理解,因为被static修饰的属于类属性,而不属于单个对象,而序列化是针对对象而言的,所以也无法进行序列化。
扩展:我们可以通过自己编写readObject()方法来提高程序的安全性。
测试代码:
private void readObject(ObjectInputStream objectInputStream) throws Exception{
objectInputStream.defaultReadObject();
if (!"男".equals(sex) && !"女".equals(sex)){
throw new RuntimeException("性别只能为男或者女");
}
}
//当我们传入一个除了男女之外的值给sex属性时
运行结果:

可以看到,我们可以根据业务需要来自己编写readObject()方法,来达到一些特定的功能。
6、序列化协议
上面我们已经介绍过了JDK中自带的序列化方式(实现Serializable接口即可),但是在实际开发中用的并不多,因为其序列化效率低,并且有些版本存在安全漏洞问题。常见的序列化协议有hessian、kyro、protostuff

浙公网安备 33010602011771号