Java—序列化—Serializable—Externalizable

众所周知,java支持对对象的序列化操作。
为什么要序列化对象呢?
总结起来也就一句话, 对一个对象的持久化 。也就是说该对象可以以某种形式保存起来,以便我在需要的时候对其反序列化操作后可以还原回来。


使用java序列化对象时, 在序列化保存对象的时候,会把其保存为一组字节,在反序列化的时候再将这些字节组装成对象。


下面来看看例子:
即将要序列化的对象,Student


需要注意的两点:

1,该对象实现了Serializable接口,凡是可以序列化的对象都需要实现接口。

2,该类中还有一个变量serialVersionUID,看它的前面的修饰符,final static 这两个修饰符还是有用的,稍后会有解释;其次,该变量的类型是long,最后,值是1L,这些稍后都有解释,先看到这里。


序列化操作:


这个方法是对对象的序列化操作,将一个student对象序列化成字节后保存到D盘中my.out的文件中。这里以文件形式保存,当然实际使用的时候还可以以其他方式保存,例如存储到数据库,或者网络传输等。


反序列化:


这是一个反序列化的方法,方法中就用了IO流中的readObject去读Student对象。最后输出该对象的各个变量的值。


下面来详细的解释一下上面程序相关的疑点:

首先是这个:serialVersionUID,这个是序列化的ID。这个ID呢,有两种生成的策略,一种是固定的1L,一种是随机生成的(JVM虚拟机随机生成),如无特殊需要,用第一种固定的即可,因为反序列化的时候是需要用到这个字段的,如果ID不相同的时候反序列化是不会成功的。再来看看这个变量前面的两个修饰符,fianl很好理解,不能再改变嘛,前面已经说了,ID不相同的话,反序列化是不会成功的,所以,ID确定之后肯定不可以再改了,最后是static这个关键字,有关static就不过多的描述了,static的变量不属于该类的对象,而是全部类共享的,所以,static的变量是不能被序列化的。

说完对象了,现在来说说读写操作吧,读写操作其实比较简单的了,也就几行代码,写的也比较简单,都很容易看懂,唯一有一点要提的是,写和读对象的时候,我们使用的是字节流,因为序列化后的是以字节数据形式保存起来的,所以要用字节流,而不能使用字符流


影响序列化的两个关键字:transient 和 static

transient:这个关键字的作用就是忽略序列化操作,也就是说,当对象中的某个属性被加上了这个关键字后,这个属性就不会被序列化。这个就不在给出代码了,自己可以去尝试一下,int类型的变量被加上这个关键字后,你反序列化得到的结果是0,String的话得到的是null。这个就不过多的解释了。

static:静态,这个要重点说说。静态的变量不属于对象,而是所有类共享的。所以,static变量也不能被序列化。好了,可以去尝试一下了,试着把前面的Student的一个属性变成静态的,然后运行一遍,你会发现,并没有被序列化。奇怪,怎么会这样呢?肯定又骗我了。因为编译器是肯定不会骗我的,哈哈。老夫怎么会骗你们呢?对吧。这里说的序列化是指序列化信息中不包含静态成员域。为什么上面这个可以测试成功呢?因为你是在同一个虚拟机里面,也就是在同一个进程里面,这个静态的域事先已经被加载进去了,所以是可以正常获取的,没错,就是这道理。那该怎么测试呢?很简单,你就新建一个测试类,把上面那个例子中的反序列化方法copy到另一个测试类中,先第一个类中运行一下序列化方法,然后再另一个测试类中运行一个反序列化方法你就会发现,反序列化出来的结果中,static的静态域是没有被序列化的。(或者你在运行完序列化方法后把eclipse关掉,然后打开后运行反序列化方法也可以,嘿嘿。)


默认序列化

什么是默认序列化呢?默认序列化也就是说,一个对象里面还有对象的话,在将这个对象序列化的时候,也会将对象里面的对象进行序列化。所以,如果一个需要序列化对象里面包含有对象,那么这个对象也必须实现序列化Serializable接口,否则在序列化的时候会报 java.io.NotSerializableException 。这点需要注意的。

默认序列化还有一点需要注意的是,如果需要序列化对象里面有容器类的属性(例如:数组或者集合等),那么,在序列化的时候,也会对容器里面的对象进行序列化。所以容器里面的对象也要实现序列化这个接口,当然,正因为会逐个地序列化容器里的对象,所以,在序列化对象的时候,如果对象里面有容器类的属性,那么序列化起来会比较慢。这点也需要注意的。


有关于writeObject和readObject

writeObject和readObject,可定制序列化,这两个方法是序列化的时候运行的,如果需要序列化的对象里面有这两个方法,就会按照这个方法去序列化操作,这时候就不用系统的序列化的方法对对象进行序列化,而是使用本类的这两个方法对其进行序列化和反序列化操作。

举个栗子:就拿上面的来说


如果上面的例子中,我把age前面加了transient,那么age还能不能被序列化呢?可以,当然可以,都是可定制了,就不受影响了。
看这两个方法,两个方法也很简单。先看wirteObject,首先输出一行,这不管,只是判断一下我这个方法有没有执行而已,第二行是将这个对象进行序列化写入操作,这也很简单,就是调用系统的序列化操作,没什么,第三行,是写入age,因为age已经被定义为transient了,所以调用defaultWriteObject是不会对age进行序列化操作了,所以这里我们对其进行序列化写入。最后是readObject这个方法。这个方法也很简单,不再过多的解释了。
下面说两点需要注意的:
1,当对象中有两个int类型的属性被定义了transient的话,该怎么做呢?这很简单嘛,按顺序,你序列化的时候是按照什么顺序写进去的,反序列化的时候也就怎么都出来,不就好了么,对吧,简单吧!
2,这两个方法必须是 私有的(private) ,否则是无效的,不信你可以去试试,这点很重要。


可定制序列化的第二种方式:Externalizable

前面已经说了一种可定制序列化的方式了,可定制序列化有什么作用呢?

都说天生我才必有用了,怎么会没用呢?例如,我要对账号密码进行序列化,那么,密码是不是不能直接是明文,要密文。如果我直接序列化的话,那不就是直接明文序列化了么,这就对了嘛,加密成密文后再序列化,对吧。

序列化,除了实现Serializable外,还可以实现Externalizable接口,这是可定制序列化,不同于Serializable的是,它需要重写writeExternal和readExternal方法,对该对象进行序列化。


例子就只给出需要序列化对象的类的代码,代码不再解释了,不同的就是这两个writeExternal和readExternal方法,这两个方法也很简单,依次的写入和读取操作,

需要注意的是:这个类中有两个构造方法,其中一个是无参的,一个是有参的,无参构造方法是一定要的,否则会在读的时候会报java.io.InvalidClassException异常。这点必须明确的,为什么要有一个有参的呢?很简单嘛,懒得去设age和name,所以直接在实例化的时候传进来咯,就这点用处,其他没什么了。

这就是其中一种可定制序列化的方法,这种方法可以按照自己的意愿去序列化需要序列话的属性,还可以先对属性进行操作后再进行序列化,反序列化的时候只需要反之就可以了,就这么简单。

最后需要提的一点是:实现Externalizable和实现Serializable相比,实现Externalizable的方式要快


序列化还可以参考一下:

Android—序列化对象—Parcelable

Java—Android—Serializable—Parcelable对比



  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值