java需要支持对象序列化_浅谈java对象序列化

一:引子

开发中我们可能会遇到这样的一种场景:有一个对象A,A中已经包含了一些有效值,此时我们可能需要一个和对象A完全相同的对象B,并且以后对对象B的改动都不会影响到对象A。也就是说A和B 是完全独立的两个对象,但是对象B的初始值是由A确定的。

我们知道对象有三种拷贝方式即:

1.引用拷贝(对象的引用)

2.浅拷贝(浅克隆)

3.深拷贝(深克隆)

那么究竟哪种方式可以满足我们以上所需的场景呢?客官莫急,我们一个个来分析下:

A.引用拷贝

简单代码实现:

其中:Student stu2 = stu1 就是对象的引用拷贝,从红色椭圆中我们可以看到stu1 与 stu2 是完全相等的。那么它能满足我们所描述的场景吗,我们验证一下:

输出结果:

可以看到改变学生2的年龄,学生1的年龄也发生了变化。故并不满足我们的描述场景。为什么会改变呢,下面这张图可以更好的理解:

原因出在(stu2 = stu1) 这一句。该语句的作用是将stu1的引用赋值给stu2,

这样,stu1和stu2指向内存堆中同一个对象。

B.浅拷贝

我们现在让student实现Cloneable接口,并重写clone方法,代码示例如下:

student类:

测试代码:

打印结果:

测试得时候发现stu1与stu2不相等,即使我们重新为stu2赋值,也不会影响stu1。咋一看,是不是似乎就满足我们引子里的场景呢。那好 现在我们再来改写下:

student实体中,引入teacher对象属性:

注意:teacher 并没有实现继承任何类或者实现任何接口。我们在用代码测试一下:

输出结果:

当把stu1克隆给stu2时,此时stu1和stu2本身还是不相等,stu1和stu2在堆中有两个不同的存储空间(地址).但是当我们分别获取stu1和stu2中的teacher对象进行比较时,我们发现此时两者是一致的 ,这就证明此时两个teacher所指向的是一个地址.这也就是浅拷贝的弊端所在,针对于引用类型,此时并不会进行地址的克隆.简单来说,在浅克隆中,当对象被复制时只复制它本身和其中包含的值类型的成员变量,而引用类型的成员对象并没有复制(并没有为引用变量分配内存空间)。

C:深拷贝

简单来说,无论原型对象的成员变量是值类型还是引用类型,都将复制一份给克隆对象,深克隆将原型对象的所有引用对象也复制一份给克隆对象。深拷贝对引用数据类型的成员变量所有对象都开辟了内存空间;而浅拷贝只是传递地址指向,新的对象并没有对引用数据类型创建内存空间。即:

实现深拷贝的两种方式:

1:可以通过覆盖Object类的clone()方法实现,即模仿浅拷贝对所有对象及引用类型实现Cloneable接口并复写clone()方法。(代码示例请自行验证)

2:对象的序列化(Serialization)

如果引用类型里面还包含很多引用类型,或者内层引用类型的类里面又包含引用类型,使用clone方法就会很麻烦。这时我们可以用序列化的方式来实现对象的深克隆。

延伸:java自带工具类BeanUtils和PropertyUtils进行对象复制()

这种写法无论多少种属性都只需要一行代码搞定,很方便吧!除BeanUtils外还有一个名为PropertyUtils的工具类,它也提供copyProperties()方法,作用与BeanUtils的同名方法十分相似,主要的区别在于BeanUtils提供类型转换功能,即发现两个JavaBean的同名属性为不同类型时,在支持的数据类型范围内进行转换,而PropertyUtils不支持这个功能,但是速度会更快一些。在实际开发中,BeanUtils使用更普遍一点,犯错的风险更低一点。

二:对象序列化

a.何为序列化与反序列化

将对象状态转化为字节流形式的过程,可以将其保存到磁盘文件中或通过网络发送到任何其他程序。从字节流创建对象的相反的过程称为反序列化。而创建的字节流是与平台无关的,在一个平台上序列化的对象可以在不同的平台上反序列化。

简单来说,就是:序列化是一种用来处理对象流的机制,所谓对象流:就是将对象的内容进行流化。可以对流化后的对象进行读写操作,也可将流化后的对象传输于网络之间。序列化是为了解决在对对象流进行读写操作时所引发的问题。

b.为什么要使用序列化:

1.对象序列化可以实现分布式对象

主要应用例如:RMI(即远程调用)要利用对象序列化运行远程主机上的服务,就像在本地机上运行对象时一样。

2.java对象序列化不仅保留一个对象的数据,而且递归保存对象引用的每个对象的数据。可以将整个对象层次写入字节流中,可以保存在文件中或在网络连接上传递。利用对象序列化可以进行对象的"深复制",即复制对象本身及引用的对象本身。序列化一个对象可能得到整个对象序列。

3.序列化可以将内存中的类写入文件或数据库中

比如:将某个类序列化后存为文件,下次读取时只需将文件中的数据反序列化就可以将原先的类还原到内存中。也可以将类序列化为流数据进行传输。总的来说就是将一个已经实例化的类转成文件存储,下次需要实例化的时候只要反序列化即可将类实例化到内存中并保留序列化时类中的所有变量和状态。

4.对象、文件、数据,有许多不同的格式,很难统一传输和保存。

列化以后就都是字节流了,无论原来是什么东西,都能变成一样的东西,就可以进行通用的格式传输或保存,传输结束以后,要再次使用,就进行反序列化还原,这样对象还是对象,文件还是文件

c.如何实现序列化:

通过实现java.io.Serializable接口,可以在Java类中启用可序列化。它是一个标记接口,意味着它不包含任何方法或字段,仅用于标识可序列化的语义。

d.简单序列化示例:

d.序列化ID(serialVersionUID:序列化版本号)

虚拟机是否允许反序列化,不仅取决于类路径和功能代码是否一致,一个非常重要的一点是两个类的序列化 ID 是否一致(就是 private static final long serialVersionUID)

序列化 ID 在 Eclipse 下提供了两种生成策略,一个是固定的 1L,一个是随机生成一个不重复的 long 类型数据(实际上是使用 JDK 工具生成),在这里有一个建议,如果没有特殊需求,就是用默认的 1L 就可以,这样可以确保代码一致时反序列化成功。那么随机生成的序列化 ID 有什么作用呢,有些时候,通过改变序列化 ID 可以用来限制某些用户的使用。

对类的哪些修改可能导致该类实例的反序列化失败呢?下面分三种情况来具体讨论:

如果修改类时仅仅修改了方法,则反序列化不受任何影响,类定义无须修改serialVersionUID Field值;

如果修改类时仅仅修改了静态(static)Field或瞬态(Transient)Field,则反序列化不受任何影响,类定义无须修改serialVersionUID Field值;

如果修改类时修改了非静态Field、非瞬态Field,则可能导致序列化版本不兼容。如果对象流中的对象和新类中包含同名的Field,而Field类型不同,则反序列化失败,类定义应该更新serialVersionUID Field值。如果对象流中的对象比新类中包含更多的Field,则多出的Field值被忽略,序列化版本可以兼容,类定义可以不更新serialVersionUID Field值;如果新类比对象流中的对象包含更多的Field,则序列化版本也可以兼容,类定义可以不更新serialVersionUID Field值;但反序列化得到的新对象中多出的Field值都是null(引用类型Field)或0(基本类型Field)。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值