代码是什么意思(序列化)
如果有更好的建议或者想看更多关于综合百科技术大全及相关资讯,可以多多关注茶馆百科网。
本文为Github开源项目:已收录github.com/hansonwang99/JavaCollection,详细的自学编程学习路线、面试问题及经典、编程资料及系列技术文章等。并且资源不断更新。
"
工具人
上次不知道哪个朋友留言说对象「序列化和反序列化」有点糊,能不能像 《枚举部分知识》 之前那样整理一波?巧了,我也是这么想的。
接到这个需求后,我又抽时间拿起尘封已久的《Java编程思想》,重新复习& quot序列化和反序列化& quot。
曾几何时,我对Java序列化的理解还停留在& quot实现可序列化的接口& quot直到。
序列化是干啥用的?
连载的初衷是& quot转换& quot一个Java对象转换成一个字节序列,便于持久存储到磁盘,防止程序运行后对象从内存中消失。另外,转换成字节序列也更便于网络传输和传播,所以概念很好理解:序列化:将Java对象转换成字节序列。反序列化:将字节序列恢复到原来的Java对象。而且序列化机制在某种意义上弥补了平台化的一些差异。毕竟转换后的字节流可以在其他平台上反序列化恢复对象。
就是这么回事。看起来很简单,但背后还有很多。请继续读下去。
对象如何序列化?
但是,Java中没有一个关键字可以直接定义一个所谓的& quot持久& quot对象。对象的持久性和反持久性需要程序员在代码中手动序列化和反序列化。
例如,假设我们要将Student类对象序列化为名为Student.txt的文本文件,然后通过该文本文件将其反序列化为Student类对象:
1.学生类别定义
public classstudentimplementserializable { privateStringname;privateIntegerageprivateIntegerscore@ OverridepublicStringtoString(){ return ' student : ' ' \ n ' ' name=' this . name ' \ n ' ' age=' this . age ' \ n ' ' score=' this . score ' \ n '}//.其他省略号.}2.序列化
publistaticvoidserialize()throwsIOException { student=new student();student . set name(' code sheep ');student . setage(18);student . set score(1000);objectoutputstream objectoutputstream=newObjectOutputStream(newFile outputstream(new file(' student . txt ')));objectOutputStream.writeObject(学生);objectoutputstream . close();System.out.println('序列化成功!student.txt文件')已生成;system . out . println('=====================================');}3.反序列化
publicstaticvoiddeserial()throwsIOException,ClassNotFoundException { objectinputstream objectinputstream=newObjectInputStream(newFile inputstream(new file(' student . txt ')));students=(Student)objectinputstream . read object();objectinputstream . close();System.out.println('反序列化结果为:');System.out.println(学生);}4、运行结果
控制台打印:
序列化成功!文件student.txt已生成===========================================反序列化结果为:Student:n。
ame=CodeSheepage=18score=1000Serializable接口有何用?
上面在定义Student类时,实现了一个Serializable接口,然而当我们点进Serializable接口内部查看,发现它竟然是一个空接口,并没有包含任何方法!
试想,如果上面在定义Student类时忘了加implementsSerializable时会发生什么呢?
实验结果是:此时的程序运行会报错,并抛出NotSerializableException异常:
我们按照错误提示,由源码一直跟到ObjectOutputStream的writeObject0()方法底层一看,才恍然大悟:
如果一个对象既不是字符串、数组、枚举,而且也没有实现Serializable接口的话,在序列化时就会抛出NotSerializableException异常!
哦,我明白了!
原来Serializable接口也仅仅只是做一个标记用!!!
它告诉代码只要是实现了Serializable接口的类都是可以被序列化的!然而真正的序列化动作不需要靠它完成。
serialVersionUID号有何用?
相信你一定经常看到有些类中定义了如下代码行,即定义了一个名为serialVersionUID的字段:
privatestaticfinallongserialVersionUID=-4392658638228508589L;
你知道这句声明的含义吗?为什么要搞一个名为serialVersionUID的序列号?
继续来做一个简单实验,还拿上面的Student类为例,我们并没有人为在里面显式地声明一个serialVersionUID字段。
我们首先还是调用上面的serialize()方法,将一个Student对象序列化到本地磁盘上的student.txt文件:
publicstaticvoidserialize()throwsIOException{Studentstudent=newStudent();student.setName("CodeSheep");student.setAge(18);student.setScore(100);ObjectOutputStreamobjectOutputStream=newObjectOutputStream(newFileOutputStream(newFile("student.txt")));objectOutputStream.writeObject(student);objectOutputStream.close();}
接下来我们在Student类里面动点手脚,比如在里面再增加一个名为studentID的字段,表示学生学号:
这时候,我们拿刚才已经序列化到本地的student.txt文件,还用如下代码进行反序列化,试图还原出刚才那个Student对象:
publicstaticvoiddeserialize()throwsIOException,ClassNotFoundException{ObjectInputStreamobjectInputStream=newObjectInputStream(newFileInputStream(newFile("student.txt")));Studentstudent=(Student)objectInputStream.readObject();objectInputStream.close();System.out.println("反序列化结果为:");System.out.println(student);}
运行发现报错了,并且抛出了InvalidClassException异常:
这地方提示的信息非常明确了:序列化前后的serialVersionUID号码不兼容!
从这地方最起码可以得出两个重要信息:
1、serialVersionUID是序列化前后的唯一标识符2、默认如果没有人为显式定义过serialVersionUID,那编译器会为它自动声明一个!第1个问题:serialVersionUID序列化ID,可以看成是序列化和反序列化过程中的“暗号”,在反序列化时,JVM会把字节流中的序列号ID和被序列化类中的序列号ID做比对,只有两者一致,才能重新反序列化,否则就会报异常来终止反序列化的过程。
第2个问题:如果在定义一个可序列化的类时,没有人为显式地给它定义一个serialVersionUID的话,则Java运行时环境会根据该类的各方面信息自动地为它生成一个默认的serialVersionUID,一旦像上面一样更改了类的结构或者信息,则类的serialVersionUID也会跟着变化!
所以,为了serialVersionUID的确定性,写代码时还是建议,凡是implementsSerializable的类,都最好人为显式地为它声明一个serialVersionUID明确值!
当然,如果不想手动赋值,你也可以借助IDE的自动添加功能,比如我使用的IntelliJIDEA,按alt+enter就可以为类自动生成和添加serialVersionUID字段,十分方便:
两种特殊情况
1、凡是被static修饰的字段是不会被序列化的2、凡是被transient修饰符修饰的字段也是不会被序列化的对于第一点,因为序列化保存的是对象的状态而非类的状态,所以会忽略static静态域也是理所应当的。
对于第二点,就需要了解一下transient修饰符的作用了。
如果在序列化某个类的对象时,就是不希望某个字段被序列化(比如这个字段存放的是隐私值,如:密码等),那这时就可以用transient修饰符来修饰该字段。
比如在之前定义的Student类中,加入一个密码字段,但是不希望序列化到txt文本,则可以:
这样在序列化Student类对象时,password字段会设置为默认值null,这一点可以从反序列化所得到的结果来看出:
序列化的受控和加强
1、约束性加持
从上面的过程可以看出,序列化和反序列化的过程其实是有漏洞的,因为从序列化到反序列化是有中间过程的,如果被别人拿到了中间字节流,然后加以伪造或者篡改,那反序列化出来的对象就会有一定风险了。
毕竟反序列化也相当于一种“隐式的”对象构造,因此我们希望在反序列化时,进行受控的对象反序列化动作。
那怎么个受控法呢?
答案就是:自行编写readObject()函数,用于对象的反序列化构造,从而提供约束性。
既然自行编写readObject()函数,那就可以做很多可控的事情:比如各种判断工作。
还以上面的Student类为例,一般来说学生的成绩应该在0~100之间,我们为了防止学生的考试成绩在反序列化时被别人篡改成一个奇葩值,我们可以自行编写readObject()函数用于反序列化的控制:
privatevoidreadObject(ObjectInputStreamobjectInputStream)throwsIOException,ClassNotFoundException{//调用默认的反序列化函数objectInputStream.defaultReadObject();//手工检查反序列化后学生成绩的有效性,若发现有问题,即终止操作!if(0>score||100<score){thrownewIllegalArgumentException("学生分数只能在0到100之间!");}}
比如我故意将学生的分数改为101,此时反序列化立马终止并且报错:
对于上面的代码,有些小伙伴可能会好奇,为什么自定义的private的readObject()方法可以被自动调用,这就需要你跟一下底层源码来一探究竟了,我帮你跟到了ObjectStreamClass类的最底层,看到这里我相信你一定恍然大悟:
又是反射机制在起作用!是的,在Java里,果然万物皆可“反射”(滑稽),即使是类中定义的private私有方法,也能被抠出来执行了,简直引起舒适了。
2、单例模式增强
一个容易被忽略的问题是:可序列化的单例类有可能并不单例!
举个代码小例子就清楚了。
比如这里我们先用java写一个常见的「静态内部类」方式的单例模式实现:
publicclassSingletonimplementsSerializable{privatestaticfinallongserialVersionUID=-1576643344804979563L;privateSingleton(){}privatestaticclassSingletonHolder{privatestaticfinalSingletonsingleton=newSingleton();}publicstaticsynchronizedSingletongetSingleton(){returnSingletonHolder.singleton;}}
然后写一个验证主函数:
publicclassTest2{publicstaticvoidmain(String[]args)throwsIOException,ClassNotFoundException{ObjectOutputStreamobjectOutputStream=newObjectOutputStream(newFileOutputStream(newFile("singleton.txt")));//将单例对象先序列化到文本文件singleton.txt中objectOutputStream.writeObject(Singleton.getSingleton());objectOutputStream.close();ObjectInputStreamobjectInputStream=newObjectInputStream(newFileInputStream(newFile("singleton.txt")));//将文本文件singleton.txt中的对象反序列化为singleton1Singletonsingleton1=(Singleton)objectInputStream.readObject();objectInputStream.close();Singletonsingleton2=Singleton.getSingleton();//运行结果竟打印false!System.out.println(singleton1==singleton2);}}
运行后我们发现:反序列化后的单例对象和原单例对象并不相等了,这无疑没有达到我们的目标。
解决办法是:在单例类中手写readResolve()函数,直接返回单例对象,来规避之:
privateObjectreadResolve(){returnSingletonHolder.singleton;}
这样一来,当反序列化从流中读取对象时,readResolve()会被调用,用其中返回的对象替代反序列化新建的对象。
没想到
本以为这篇会很快写完,结果又扯出了这么多东西,不过这样一梳理、一串联,感觉还是清晰了不少。
就这样吧,下篇见。
本文Github开源项目:github.com/hansonwang99/JavaCollection中已收录,有详细自学编程学习路线、面试题和面经、编程资料及系列技术文章等,资源持续更新中...
”
每天进步一点点,Peace
慢一点,才能更快
本文主要介绍了关于代码是什么意思(序列化)的相关养殖或种植技术,综合百科栏目还介绍了该行业生产经营方式及经营管理,关注综合百科发展动向,注重系统性、科学性、实用性和先进性,内容全面新颖、重点突出、通俗易懂,全面给您讲解综合百科技术怎么管理的要点,是您综合百科致富的点金石。
以上文章来自互联网,不代表本人立场,如需删除,请注明该网址:http://seotea.com/article/87014.html