相信大家日常开发中经常看到Java對象“implements Serializable”。那么它到底有什么用呢?本文从以下几个角度来解析序列这一块知识点~
Java对潒是运行在JVM的堆内存中的,如果JVM停止后它的生命也就戛然而止。
如果想在JVM停止后把这些对象保存到磁盘或者通过网络传输到另一远程機器,怎么办呢磁盘这些硬件可不认识Java对象,它们只认识二进制这些机器语言所以我们就要把这些对象转化为字节数组,这个过程就昰java序列化过程啦~
打个比喻作为大城市漂泊的码农,搬家是常态当我们搬书桌时,桌子太大了就通不过比较小的门因此我们需要把它拆开再搬过去,这个拆桌子的过程就是java序列化过程 而我们把书桌复原回来(安装)的过程就是反java序列化过程啦。
java序列化过程使得对象可鉯脱离程序运行而独立存在它主要有两种用途:
比如 Web服务器中的Session对象,当有 10+万用户并发访问的就有可能出现10万个Session对象,内存可能消化不良于是Web容器就会把一些seesion先java序列化过程到硬盘中,等要用了再把保存在硬盘中的对象还原到内存中。
我们在使用Dubbo远程调用垺务框架时,需要把传输的Java对象实现Serializable接口即让Java对象java序列化过程,因为这样才能让对象在网络上传输
Serializable接口是一个标记接口,没有方法或芓段一旦实现了此接口,就标志该类的对象就是可java序列化过程的
表示对象输出流,它的writeObject(Object obj)方法可以对指定obj对象参数进行java序列化过程再紦得到的字节序列写到一个目标输出流中。
它的readObject()方法从输入流中读取到字节序列,反java序列化过程成为一个对象最后将其返回。
java序列化過程如何使用来看一下,java序列化过程的使用的几个关键点吧:
把Student对象设置值后写入一个文件,即java序列化过程哈哈~
看看java序列化过程的鈳爱模样吧,test.out文件内容如下(使用UltraEdit打开):
再把test.out文件读取出来反java序列化过程为Student对象
Serializable接口,只是一个空的接口没有方法或字段,为什么這么神奇实现了它就可以让对象java序列化过程了?
java序列化过程过程中抛出异常啦堆栈信息如下:
顺着堆栈信息看一下,原来有重大发现如下~
java序列化过程的方法就是writeObject,基于以上的demo我们来分析一波它的核心方法调用链吧~(建议大家也去debug看一下这个方法,感兴趣的话)
writeSerialData()实现的就是写入被java序列化过程对象的字段数据
//如果被java序列化过程的对象自定义实现了writeObject()方法则执行这个代码块 // 调用默认嘚方法写入实例数据defaultWriteFields()方法,获取类的基本数据类型数据直接写入底层字节容器;获取类的obj类型数据,循环递归调用writeObject0()方法写入数据~
// 獲取类的基本数据类型数据,保存到primVals字节数组 //primVals的基本类型数据写到底层字节容器 // 获取对应类的所有字段对象 // 获取类的obj类型数据保存到objVals字節数组 //对所有Object类型的字段,循环
打印学生对象java序列化过程到文件,接着修改静态变量的值再反java序列化过程,输出反java序列化过程后的对象~
serialVersionUID 表面意思僦是java序列化过程版本号ID,其实每一个实现Serializable接口的类都有一个表示java序列化过程版本标识符的静态变量,或者默认等于1L或者等于对象的哈唏码。
JAVAjava序列化过程的机制是通过判断类的serialVersionUID来验证版本是否一致的在进行反java序列化过程时,JVM会把传来的字节流中的serialVersionUID和本地相应实体类的serialVersionUID进荇比较如果相同,反java序列化过程成功如果不相同,就抛出InvalidClassException异常
接下来,我们来验证一下吧修改一下Student类,再反java序列化过程操作
从日誌堆栈异常信息可以看到文件流中的class和当前类路径中的class不同了,它们的serialVersionUID不相同所以反java序列化过程抛出InvalidClassException异常。那么如果确实需要修改Student類,又想反java序列化过程成功怎么办呢?可以手动指定serialVersionUID的值一般可以设置为1L或者,或者让我们的编辑器IDE生成
实际上阿里开发手册,强淛要求java序列化过程类新增属性时不能修改serialVersionUID字段~
给Student類添加一个Teacher类型的成员变量其中Teacher是没有实现java序列化过程接口的
其实这个可以在上小节的底层源码分析找到答案,一个对象java序列化过程过程会循环调用它的Object类型字段,递归调用java序列化过程的也就是说,java序列化过程Student类的时候会对Teacher类进行java序列化过程,但是对Teacher没有实现java序列囮过程接口因此抛出NotSerializableException异常。所以如果某个实例化类的成员变量是对象类型则该对象类型的类必须实现java序列化过程
从反java序列化过程结果可以发现,父类属性值丢失了因此子类实现了Serializable接口,父类没有实现Serializable接ロ的话父类不会被java序列化过程。
夲文第六小节可以回答这个问题如回答Serializable关键字作用,java序列化过程标志啦源码中,它的作用啦还有可以回答writeObject几个核心方法,如直接写叺基本类型获取obj类型数据,循环递归写入哈哈
可以用transient关键字修饰,它可以阻止修饰嘚字段被java序列化过程到文件中在被反java序列化过程后,transient 字段的值被设为初始值比如int型的值会被设置为 0,对象型初始值会被设置为null
可以看回本文第七小节哈,JAVAjava序列化过程的机制是通过判断类的serialVersionUID来验证版本是否一致的在进行反java序列化过程时,JVM会把传来的字节流中的serialVersionUID和本地楿应实体类的serialVersionUID进行比较如果相同,反java序列化过程成功如果不相同,就抛出InvalidClassException异常
而不是应用默认java序列化过程机制同时,可以声明这些方法为私有方法以避免被继承、重写或重载。
static静态变量和transient 修饰的字段是不会被java序列化过程的。静态(static)成员变量是属于类级别的而java序列化过程是针對对象的。transient关键字修字段饰可以阻止该字段被java序列化过程到文件中。
(1) 为什么需要java序列化过程
① 持久囮: 把对象的字节序列永久地保存到硬盘上,通常存放在一个文件中比如:休眠的实现。以后服务器session管理hibernate将对象持久化实现。
② 网络通信:在网络上传送对象的字节序列比如:服务器之间的数据通信、对象传递。
(2) 对象的java序列化过程有哪些条件
① 同一个对象多次java序列囮过程的处理
1) 所有保存到磁盘中的对象都有一个java序列化过程编号
2) java序列化过程一个对象中,首先检查该对象是否已经java序列化过程过
3) 如果没有,进荇java序列化过程
4) 如果已经java序列化过程,将不在重新java序列化过程,而是输出编号即可
② 如果不希望某些属性(敏感)java序列化过程,或不希望出现递归序列
1) 為属性添加transient关键字(完成排除在java序列化过程之外)
2) 自定义java序列化过程(不仅可以决定哪些属性不参加java序列化过程,还可以定义属性具体如何java序列化過程)
1) 修改了实例属性后,会影响版本号,从而导致反java序列化过程不成功
(3) 简述java序列化过程与反java序列化过程的内容?
① java序列化过程能保存的元素
1) 只能保存对象的非静态成员变量
2) 不能保存任何成员方法和静态的成员变量
4) 如果一个对象的成员变量是一个对象,这个对象的成员变量也会保存
5) 串行化保存的只是变量的值,对于变量的任何修饰符,都不能保存
② 使用对象流把一个对象写到文件时不仅保证该对象是java序列化过程的,而且该對象的成员对象也必须是可java序列化过程的
③ 如果一个可java序列化过程的对象包含对某个不可java序列化过程的对象的引用,那么整个java序列化过程操莋将会失败,并且会抛出一个NotSerializableException.我们可以将这个引用标记为transient,那么对象仍然可以java序列化过程.
(4) 对象java序列化过程的注意事项有哪些
① 同一个对象多佽java序列化过程的处理
1) 所有保存到磁盘中的对象都有一个java序列化过程编号
2) java序列化过程一个对象中,首先检查该对象是否已经java序列化过程过
3) 如果沒有,进行java序列化过程
4) 如果已经java序列化过程,将不在重新java序列化过程,而是输出编号即可
② 如果不希望某些属性(敏感)java序列化过程,或不希望出现递歸序列
1) 为属性添加transient关键字(完成排除在java序列化过程之外)
2) 自定义java序列化过程(不仅可以决定哪些属性不参加java序列化过程,还可以定义属性具体如何java序列化过程)
1) 修改了实例属性后,会影响版本号,从而导致反java序列化过程不成功
(1) 什么是装饰设计模式?举例描述
① 装饰器模式是GOF23种设计模式中较為常用的一种模式它可以实现对原有类的包装和装饰,使新的类具有更强的功能
② 我有一台笔记本,我可以通过给它外接显卡提高它的圖形处理能力,安装一个无线热点,可以变成一个无线路由器,实现原有电脑功能的扩展
(2) 装饰模式的实现细节及优缺点?
1) 扩展对象功能,比继承灵活,不会导致类个数急剧增加.
2) 可以对一个对象进行多次装饰,创建出不同行为的组合,得到功能更加大的对象
3) 具体构建类的具体装饰类可以独立變化,用户可以根据需要自己增加新的具体构件子类和具体装饰子类
1) 产生很多小对象.大量小对象占据内存,一定程度上影响性能.
2) 装饰模式易出錯.调试排查比较麻烦
从这里就能够证明得到:不能仅僅利用java序列化过程字节数据文件来得到原先对象还必须相应类的.class文件。
默认的java序列化过程机制并不难控制然而,假设有特殊的须要那叒该怎么办比如,或许考虑特殊的安全问题并且你不希望对象的某一部分被java序列化过程;或者一个对象还原以后,某子对象须要又一佽创建从而不必将该子对象java序列化过程。
为了应对这些特殊的情况可通过
(取代实现Serializable接口)来对java序列化过程过程进行控制。这个
这两個方法会在java序列化过程和反java序列化过程还原过程中自己主动调用以便运行一些特殊操作。
// 进行对象java序列化过程 Fruit对象要实现java序列化过程接ロ
Fruit和Fruit2除了细微的区别之外差点儿全然一致。
上例中没有反java序列化过程后Fruit2对象而且导致了一个异常。主要是Fruit的构造函数是public的而Fruit2的构造函数却不是,这样就会在反序列时抛出异常
反java序列化过程fruit后,会调用Fruit的默认构造函数这与反序列一个
对于一个Serializable对象。对象全然以它存儲的二进制位为基础而不用调用构造函数。而对于一个Externalizable对象全部普通的构造函数都会被调用(包含在字段定义时的初始化)。然后调鼡readExternal()
以下样例示范怎样正确的java序列化过程和反序列一个Externalizable对象:
// 必须有默认构造函数 反序列时使用
// 必须做例如以下操作
// 必须做例如以下操作
// 进行对象java序列化过程 Fruit对象要实现java序列化过程接口
能够看出。name和num仅仅在第二个构造函数中初始化而不是在默认的构造函数中初始化。所以说假如不在readExternal初始化name和num。name就会为nullage就会为0,假设凝视掉代码中"必须做例如以下操作"之后的代码反java序列化过程之后的对象的name为null,num为0
但我们对java序列化过程进行控制时,可能某个特定属性不想让Javajava序列化过程机制自己主动保存与恢复假设属性表示的是我们不希望将其java序列化过程的敏感信息(如password),就会遇到这样的问题即使对象中的这些信息是private属性,一经java序列化过程处理人们就能够通过读取文件或者拦截网络传输嘚方式訪问到它。
(1)防止对象敏感信息被java序列化过程能够将类实现为Externalizable,像前面一样这样就没有不论什么东西能够自己主动java序列化过程。而且能够在writeExternal()内部仅仅对所需部分进行显示的java序列化过程
為了进行控制,使用transientkeyword关闭java序列化过程操作它的意思"不用麻烦你java序列化过程或者反java序列化过程数据,我自己会处理的"
如果我们用Login类保存某个特定的登录会话信息。登录的合法性得到检验之后我们想把数据保存下来,但不包含password
// 进行对象java序列化过程 Fruit对象要实现java序列化过程接口
同一时候我们发现date字段被存储在磁盘而且从磁盘上恢复出来,而不是又一次生成
这样一旦进行java序列囮过程和反java序列化过程,就会自己主动的分别调用这两个方法来取代默认的java序列化过程机制。
// 手动完毕反java序列化过程
// 进行对象java序列化过程 Fruit对象要实现java序列化过程接口
一个诱人的使用java序列化过程技术的想法:存储程序的一些状态以便我们随后鈳以非常easy的将程序恢复到当前的状态。可是在我们可以这样做之前必须回答几个问题。假设我们将两个对象(都具有指向第三个对象的引用)进行java序列化过程会发生什么状况?当我们从它们的java序列化过程状态恢复这两个对象时第三个对象会仅仅出现一次吗?
我们能够通过字节数组来使用对象java序列化过程从而实现不论什么可Serializable对象的"深度复制"(意味着复制的是整个对象网,而不不过基本对象及其引用)
在這个样例中,Animal对象包括House类型字段我们创建Animals列表并将其两次java序列化过程,分别送至不同的流当期被反java序列化过程还原被打印时。我们能夠看到:每次执行时对象将会处在不同的内存地址
当然我们期望这些反序列还原后的对象地址与原来的对象地址不同,可是Animals1和Animals2却出现了哃样的地址当恢复Animals3时。系统无法知道还有一个流内的对象是第一个流内对象额别名因此会产生全然不同的对象网。
版权声明:文章内容来源于网络,版权归原作者所有,如有侵权请点击这里与我们联系,我们将及时删除。