ObjectOutputStream和ObjectInputStream对对象进行序列化和反序列化
1 Java序列化和反序列化简介
Java序列化是指把对象转换为字节序列的过程,而Java反序列化是指把字节序列恢复为java对象的过程。
我们把对象序列化成有序字节流,保存到本地磁盘或者Redis等媒介中,或者直接通过网络传输进行远程方法调用(RMI)来使用,在使用的时候再进行反序列化来得到该对象
2 通过Serializable实现序列化
在Java中,只要一个类实现了了java.io.Serializable接口,那么这个类就可以被序列化。
查看Serializable接口,会发现该接口中并没有任何代码,这个实现Serializable接口仅仅作为一个标志。
2.1 ObjectOutputStream和ObjectInputStream对对象进行序列化和反序列化
通过ObjectOutputStream和ObjectInputStream对对象进行序列化及反序列化。调用ObjectOutputStream对象的writeObject输出可序列化对象,调用ObjectInputStream对象的readObject()得到序列化的对象。
public class Person implements Serializable {
//序列化并不保存静态变量
static String a = "hello";
static int b = 200;
int id;
String name;
//只定义有参构造器
public Person(int id, String name) {
System.out.println("反序列化会调用构造器吗?");
this.id = id;
this.name = name;
}
@Override
public String toString() {
return "Person{" + "id=" + id + ", name='" + name + '\'' + '}';
}
}
在Person中,只定义了有参构造器。
public class SerializableTest {
public static void main(String[] args) throws Exception {
dome1();
dome2();
}
private static void dome1() throws Exception{
File f = new File("test.txt");
if(!f.exists())
f.createNewFile();
ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream(f));
objectOutputStream.writeObject(new Person(1, "Tom"));
objectOutputStream.close();
}
private static void dome2() throws Exception{
File f = new File("test.txt");
ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream(f));
Object object = objectInputStream.readObject();
System.out.println(object.toString());
objectInputStream.close();
}
}
执行结果:
反序列化会调用构造器吗?
Person{id=1, name='Tom'}
可以看到只会打印一次“反序列化会调用构造器吗?”,说明反序列化并不会使用构造器来生成对象,跟踪反序列化代码,最终执行到:
obj = desc.isInstantiable() ? desc.newInstance() : null;
desc.newInstance()方法原理是利用反射创建了一个对象,本质是调用非序列化父类的无参构造器。
如果该类是Serializable类型的,则调用该类的第一个非Serializable类型的父类的无参构造方法。这里最终会调用Object类的无参构造器,在JDK1.8中,Object中没有构造器,系统会自动生成一个无参构造器。
我们添加一个Parent类,Parent没有实现Serializable,再让Person类继承它,执行dome1和dome2方法:
public class Parent {
public Parent(){
System.out.println("父类构造器");
}
}
执行结果:
父类构造器
反序列化会调用构造器吗?
父类构造器
Person{id=1, name='Tom'}
可以看到反序列化调用了没有实现Serializable的Parent类的构造器,我们再将Parent 实现Serializable,执行dome1和dome2方法:
父类构造器
反序列化会调用构造器吗?
Person{id=1, name='Tom'}
发现并没有调用Parent类的构造器了,这里会继续向上找到Object类,并调用构造方法创建对象,并逐层向下去通过反射设置可以被反序列化的属性。
我们不去继承Parent类,重新执行dome1和dome2方法:
找到test.txt文本,打开可以看出静态变量并不会被序列化。
2.2 serialVersionUID
这里我们在Person在增加两个属性如下,其中email使用**transient **修饰:
public class Person implements Serializable {
//序列化并不保存静态变量
static String a = "hello";
static int b = 200;
int id;
String name;
int age;
transient String email;
//只定义有参构造器
public Person(int id, String name, int age, String email) {
this.id = id;
this.name = name;
this.age = age;
this.email = email;
}
@Override
public String toString() {
return "Person[" + "id=" + id + ", name='" + name + '\'' + ", age=" + age + ", email='" + email + '\'' + ']';
}
}
然后只运行dome2(),测试能否可以从上次保存的文本内容中反序列化成功?

从异常信息中可以明显看出问题:本地保存和读取的流中 serialVersionUID不匹配。
serialVersionUID是记录class文件的版本信息的,这个数字是JVM通过一个类的类名、成员、包名、工程名算出的一个数字。
而这时候序列化文件中记录的serialVersionUID与项目中的不一致,即找不到对应的类来反序列化。从而抛出InvalidClassException,InvalidClassException是一个IOException。
解决方法:给需要序列化的类指定一个serialVersionUID,在序列化与反序列化的时候,JVM都不会再自己算这个class的serialVersionUID了
我们给Person类加上serialVersionUID,来重新序列化,然后再添加两个属性,再进行反序列可以得到执行结果:
Person[id=1, name='Tom', age=0, email='null']
可以看到反序列成功了,所以如果在类中指定了serialVersionUID,后期可以修改这个类,还是可以成功的反序列化,若我们保存数据在Redis中,后期添加了类属性,在没删除缓存的情况下,还是可以从Redis中获取对象。
我们改造dome1,然后运行dome1():
private static void dome1() throws Exception{
File f = new File("test.txt");
if(!f.exists())
f.createNewFile();
ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream(f));
objectOutputStream.writeObject(new Person(1, "Tom", 25, "78451248788@qq.com"));
objectOutputStream.close();
}
找到test.txt文本,可以看到age属性,但是email属性没有,这是因为transient修饰的原因,transient修饰的属性,是不会被序列化的。
所以反序列化出的对象,被transient修饰的属性是默认值。对于引用类型,值是null;基本类型,值是0;boolean类型,值是false。
这里提一点,从 email='null' 可以看出String类型是被当成引用类型的。
再添加dome3和dome4方法,并运行:
private static void dome3() throws Exception{
File f = new File("test.txt");
if(!f.exists())
f.createNewFile();
ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream(f));
Person person = new Person(1, "Tom", 25, "78451248788@qq.com");
//写入两次
objectOutputStream.writeObject(person);
objectOutputStream.writeObject(person);
objectOutputStream.close();
}
private static void dome4() throws Exception{
File f = new File("test.txt");
ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream(f));
//读取两次
Object object1 = objectInputStream.readObject();
Object object2 = objectInputStream.readObject();
Person p1 = (Person) object1;
Person p2 = (Person) object2;
System.out.println(p1 == p2);
objectInputStream.close();
}
执行结果:
true
可以看出Java序列化同一对象,并不会将此对象序列化多次得到多个对象。
2.3 自定义序列化,写入时替换对象writeReplace
在序列化时,会先调用此方法,再调用writeObject方法。此方法可将任意对象代替目标序列化对象,在我们需要改变序列化对象时使用。
在person中添加writeReplace方法:
private Object writeReplace(){
List<Object> list = new ArrayList<>();
list.add(id);
list.add(name);
list.add(age);
list.add(email);
return list;
}
这里将对象替换成一个List,在序列化的时候会调用writeReplace将对象换成List再存入文本中,反序列的时候获得的不再是person对象,而是List对象。
2.4 自定义反序列化,恢复对象时替换readResolve
反序列化时替换反序列化出的对象,反序列化出来的对象被立即丢弃。readResolve在readeObject后调用。
我们注释上面的writeReplace方法,添加readResolve方法,执行方法:
private Object readResolve()throws ObjectStreamException {
return "恢复对象时直接替换结果";
}
执行结果:
恢复对象时直接替换结果
可以看到结果已经被替换了。writeObject与readObject需成对实现,而writeReplace与readResolve则不需要成对出现,一般单独使用。
2.5 readResolve保护单例和枚举类型
定义单例对象:
public class Singleton implements Serializable {
private Singleton(){}
private static Singleton instance = new Singleton();
public static Singleton getInstance(){
return instance;
}
}
添加dome5方法,并执行:
private static void dome5() throws Exception{
File f = new File("test.txt");
if(!f.exists())
f.createNewFile();
Singleton singleton = Singleton.getInstance();
ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream(f));
objectOutputStream.writeObject(singleton);
objectOutputStream.close();
//反序列化一次
ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream(f));
Singleton s1 = (Singleton) objectInputStream.readObject();
objectInputStream.close();
//反序列化两次
ObjectInputStream objectInputStream2 = new ObjectInputStream(new FileInputStream(f));
Singleton s2 = (Singleton) objectInputStream2.readObject();
objectInputStream.close();
System.out.println(singleton == s1);
System.out.println(s1 == s2);
}
执行结果:
false
false
在单例方法中添加readResolve方法,直接返回原单例对象,再执行dome5方法:
public class Singleton implements Serializable {
private Singleton(){}
private static Singleton instance = new Singleton();
public static Singleton getInstance(){
return instance;
}
private Object readResolve(){
return instance;
}
}
执行结果:
true
true
可以看到反序列出来还是原来的单例对象,保护了单例。
ObjectOutputStream和ObjectInputStream对对象进行序列化和反序列化的更多相关文章
- 我的Java开发学习之旅------>Java使用ObjectOutputStream和ObjectInputStream序列号对象报java.io.EOFException异常的解决方法
今天用ObjectOutputStream和ObjectInputStream进行对象序列化话操作的时候,报了java.io.EOFException异常. 异常代码如下: java.io.EOFEx ...
- java中对象的序列化和反序列化
[对象的序列化和反序列化 ] 1.定义:序列化--将对象写到一个输出流中.反序列化则是从一个输入流中读取一个对象.类中的成员必须是可序列化的,而且要实现Serializable接口,这样的类的对象才能 ...
- 深入理解Java对象的序列化与反序列化的应用
当两个进程在进行远程通信时,彼此可以发送各种类型的数据.无论是何种类型的数据,都会以二进制序列的形式在网络上传送.发送方需要把这个Java对象转换为字节序列,才能在网络上传送:接收方则需要把字节序列再 ...
- Java对象的序列化与反序列化
序列化与反序列化 序列化 (Serialization)是将对象的状态信息转换为可以存储或传输的形式的过程.一般将一个对象存储至一个储存媒介,例如档案或是记亿体缓冲等.在网络传输过程中,可以是字节或是 ...
- Java对象的序列化和反序列化[转]
Java基础学习总结--Java对象的序列化和反序列化 一.序列化和反序列化的概念 把对象转换为字节序列的过程称为对象的序列化.把字节序列恢复为对象的过程称为对象的反序列化. 对象的序列化主要有两种用 ...
- 【Java IO流】对象的序列化和反序列化
对象的序列化和反序列化 1)对象序列化,就是将Object对象转换成byte序列,反之叫对象的反序列化. 2)序列化流(ObjectOutputStream),是字节的过滤流—— writeObjec ...
- Java—IO流 对象的序列化和反序列化
序列化的基本操作 1.对象序列化,就是将Object转换成byte序列,反之叫对象的反序列化. 2.序列化流(ObjectOutputStream),writeObject 方法用于将对象写入输出流中 ...
- 对象的序列化与反序列化---IO学习笔记(四)
对象的序列化,反序列化 对象的序列化: 就是将Object转换成byte序列 对象的反序列化: 将byte序列转换成Object 序列化流.反序列化流 序列化流(ObjectOutputStream) ...
- (记录)Jedis存放对象和读取对象--Java序列化与反序列化
一.理论分析 在学习Redis中的Jedis这一部分的时候,要使用到Protostuff(Protobuf的Java客户端)这一序列化工具.一开始看到序列化这些字眼的时候,感觉到一头雾水.于是,参考了 ...
随机推荐
- [PHP学习教程 - 日期/时间]001.月份第一天&最后一天(Month First Day & Last Day)
引言:在时间处理上,对于前/后台人性化的展示当前月份最大天数,这个是网站必须要处理的一个方面.但通常这一块会由第三方类库直接包装,这里我们做一个简单的Mark. 今天,我们就为大家提供一个函数,获得指 ...
- SD.Team字符表情集大全(持续更新中..)
一.超级可爱的字符表情集 01. <( ̄︶ ̄)> 02. <( ̄︶ ̄)/ 03. b( ̄▽ ̄)d 04. 汗( ̄口 ̄)!! 05. ╮( ̄▽ ̄)╭ 06. ╰( ̄▽ ̄)╭ 07. ╮ ...
- [工具-004]如何从apk中提取AndroidManifest.xml并提取相应信息
跟上一篇类似,我们也需要对APK的一些诸如umengkey,ADkey,TalkingData进行验证,那么我们同样需要解压apk文件,然后提取其中的AndroidManifest.xml.然后解析x ...
- 50个SQL语句(MySQL版) 问题八
--------------------------表结构-------------------------- student(StuId,StuName,StuAge,StuSex) 学生表 tea ...
- Java实现 蓝桥杯 算法提高 P0101
算法提高 P0101 时间限制:1.0s 内存限制:256.0MB 提交此题 一个水分子的质量是3.0*10-23克,一夸脱水的质量是950克.写一个程序输入水的夸脱数n(0 <= n &l ...
- Java实现 LeetCode 382 链表随机节点
382. 链表随机节点 给定一个单链表,随机选择链表的一个节点,并返回相应的节点值.保证每个节点被选的概率一样. 进阶: 如果链表十分大且长度未知,如何解决这个问题?你能否使用常数级空间复杂度实现? ...
- Java实现蓝桥杯墓地雕塑
墓地雕塑 问题描述 在一个周长为10000的圆上等距分布着n个雕塑.现在又有m个新雕塑加入(位置可以随意放), 希望所有n+m个雕塑在圆周上均匀分布.这就需要移动其中一些原有的雕塑.要求n个雕塑移动的 ...
- Java实现 LeetCode 205 同构字符串
205. 同构字符串 给定两个字符串 s 和 t,判断它们是否是同构的. 如果 s 中的字符可以被替换得到 t ,那么这两个字符串是同构的. 所有出现的字符都必须用另一个字符替换,同时保留字符的顺序. ...
- Java实现 洛谷 P1085 不高兴的津津
import java.io.*; import java.util.*; class Main{ public static void main(String args[]) { Scanner s ...
- Python学习之斐波那契数列实现篇
描述 一个斐波那契序列,F(0) = 0, F(1) = 1, F(n) = F(n-1) + F(n-2) (n>=2),根据n的值,计算斐波那契数F(n),其中0≤n≤1000. 输入 输入 ...