前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >为什么很多类甚者底层源码要implements Serializable ?

为什么很多类甚者底层源码要implements Serializable ?

作者头像
bear_fish
发布2018-09-19 15:42:59
2.5K0
发布2018-09-19 15:42:59
举报

为什么很多类甚者底层源码要implements Serializable ?

在碰到异常类RuntimeException时,发现Throwable实现了 Serializable,还有我们平进的javabean一般也要实现Serializable,不明白为什么?找了下资料,有的来自其他博客,如有侵犯你的版权,告知我,我将删掉,我的目的只是给自己做个小总结如下:

public class Throwable implements Serializable {

/** use serialVersionUID from JDK 1.0.2 for interoperability */

private static final long serialVersionUID = -3042686055658047285L;

serialization 允许你将实现了Serializable接口的对象转换为字节序列,这些字节序列可以被完全存储以备以后重新生成原来的对象。

serialization不但可以在本机做,而且可以经由网络操作。这个好处是很大的----因为它自动屏蔽了操作系统的差异,字节顺序等。比如,在Window平台生成一个对象并序列化之,然后通过网络传到一台Unix机器上,然后可以在这台Unix机器上正确地重构这个对象。

Object serialization主要用来支持2种主要的特性:

1.Java的RMI(remote method invocation).RMI允许象在本机上一样操作远程机器上的对象。当发送消息给远程对象时,就需要用到serializaiton机制来发送参数和接收返回直。

2.Java的JavaBeans. Bean的状态信息通常是在设计时配置的。Bean的状态信息必须被存起来,以便当程序运行时能恢复这些状态信息。这也需要serializaiton机制。

总之如果在网络的环境下做类传输,应该还是implements Serializable。没有implements Serializable,你就不能通过rmi(包括ejb)提供远程调用。

再如一个C/s(socket)程序,client显示server段的数据,通常情况下你可以在server用取得socket的outputstream inputstream,将取得的数据处理成字符串到客户端,然后在客户端分拆字符串,但是这样显然降低效率,于是你可以将server 端数据包装成一个class implements Serializable,然后直接用objectoutputstream,objectinputstream直接传递。

Serizlizable 作用

对于一个存在于Java虚拟机中的对象来说,其内部的状态只保持在内存中。JVM停止之后,这些状态就丢失了。在很多情况下,对象的内部状态是需要被持久化下来的。提到持久化,最直接的做法是保存到文件系统或是数据库之中。比如:对象关系映射(Object-relational mapping)。对象序列化机制(object serialization)是Java语言内建的一种对象持久化方式,可以很容易的在JVM中的活动对象和字节数组(流)之间进行转换。除了可以很简单的实现持久化之外,另外序列化机制的另外一个重要用途是在远程方法调用中,用来对开发人员屏蔽底层实现细节。

基本的对象序列化

待序列化的Java类只需要实现Serializable接口即可。实际的序列化和反序列化工作是通过ObjectOuputStream和ObjectInputStream来完成的。ObjectOutputStream的writeObject方法可以把一个Java对象写入到流中,ObjectInputStream的readObject方法可以从流中读取一个Java对象。在写入和读取的时候,虽然用的参数或返回值是单个对象,但实际上操纵的是一个对象图,包括该对象所引用的其它对象,以及这些对象所引用的另外的对象。Java会自动帮你遍历对象图并逐个序列化。除了对象之外,Java中的基本类型和数组也是可以通过 ObjectOutputStream和ObjectInputStream来序列化的。

序列化时的对象替换

可能会希望在序列化的时候使用另外一个对象来代替当前对象。其中的动机可能是当前对象中包含了一些不希望被序列化的域。个订单系统中需要把订单的相关信息序列化之后,通过网络来传输。订单类Order引用了客户类Customer。在默认序列化的情况下,Order类对象被序列化的时候,其引用的Customer类对象也会被序列化,这可能会造成用户信息的泄露。

Private static class OrderReplace implements Serializable{

Private static final long serialVersionUID=47832438;

Private String orderId;

Public OrderReplace(Order order){

This.orderId = order.getId();

}

Private Object readResolve(){

//根据orderId查找Order 对象并返回。

}

Public Object writeReplace(){

Return new OrderReplace(this);

}

}

这个替换对象类OrderReplace只保存了Order的ID。在Order类的writeReplace方法中返回了一个OrderReplace对象。这个对象会被作为替代写入到流中。同样的,需要在OrderReplace类中定义一个readResolve方法,用来在读取的时候再转换回 Order类对象。这样对调用者来说,替换对象的存在就是透明的。

序列化和对象创建

在通过ObjectInputStream的readObject方法读取到一个对象之后,这个对象是一个新的实例,但是其构造方法是没有被调用的,其中的域的初始化代码也没有被执行。调用者并不知道对象是通过一般的new操作符来创建的,还是通过反序列化所得到的。解决的办法就是在类的readObject方法里面,再执行所需的对象初始化逻辑。对于一般的Java类来说,构造方法中包含了初始化的逻辑。可以把这些逻辑提取到一个方法中,在readObject方法中调用此方法。

版本更新

把一个Java对象序列化之后,所得到的字节数组一般会保存在磁盘或数据库之中。在保存完成之后,有可能原来的Java类有了更新,比如添加了额外的域。这个时候从兼容性的角度出发,要求仍然能够读取旧版本的序列化数据。在读取的过程中,当ObjectInputStream发现一个对象的定义的时候,会尝试在当前JVM中查找其Java类定义。这个查找过程不能仅根据Java类的全名来判断,因为当前JVM中可能存在名称相同,但是含义完全不同的Java 类。这个对应关系是通过一个全局惟一标识符serialVersionUID来实现的。通过在实现了Serializable接口的类中定义该域,就声明了该Java类的一个惟一的序列化版本号。JVM会比对从字节数组中得出的类的版本号,与JVM中查找到的类的版本号是否一致,来决定两个类是否是兼容的。对于开发人员来说,需要记得的就是在实现了Serializable接口的类中定义这样的一个域,并在版本更新过程中保持该值不变。当然,如果不希望维持这种向后兼容性,换一个版本号即可。该域的值一般是综合Java类的各个特性而计算出来的一个哈希值。可以通过Java提供的serialver命令来生成。在Eclipse中,如果Java类实现了Serializable接口,Eclipse会提示并帮你生成这个serialVersionUID。

例如:

public class Book implements Serializable {

//private static final long serialVersionUID = 5805574648076667828L;

String name="chenlly";

//int vale = 7;

//序列化写入文件

public void save() throws IOException {

FileOutputStream f = new FileOutputStream("d://book.bin");

ObjectOutputStream oos = new ObjectOutputStream(f);

oos.writeObject(this);

oos.close();

}

//反序列化从文件读取

public void read() throws IOException, ClassNotFoundException {

FileInputStream f = new FileInputStream("d://book.bin");

ObjectInputStream ois = new ObjectInputStream(f);

Book b = (Book) ois.readObject();

ois.close();

}

public static void main(String []args){

Book b = new Book();

try {

//序列化

b.save();

//读取

b.read();

} catch (Exception e) {

// TODO Auto-generated catch block

e.printStackTrace();

}

}

}

如果增加int vale = 7;,把b.save()注释掉,再次执行程序则会抛出如下异常。

java.io.InvalidClassException: cn.com.chenlly.Book; local class incompatible: stream classdesc serialVersionUID = -8127777334808352611, local class serialVersionUID = 6452469819260868051

java 默认情况下serialVersionUID是是综合Java类的各个特性而计算出来的一个哈希值。

两次的属性值一样导致serialVersionUID也不一样。

如果加上private static final long serialVersionUID = 5805574648076667828L则这个类都有一个唯一标识,两次的serialVersionUID不变。反序列化中还是不会报错。

如果把字符串改成int name=34; 执行逆-串行化操作时系统就不知道如何处理该值,显示出错误信息:java.io.InvalidClassException: cn.com.chenlly.Book; incompatible types for field name。

简而言之,如果文件中确实保存了所有必需的数据,那么仍有可能读取该文件,当然前提是必须处理好串行化的UID。

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2016年09月01日,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
相关产品与服务
对象存储
对象存储(Cloud Object Storage,COS)是由腾讯云推出的无目录层次结构、无数据格式限制,可容纳海量数据且支持 HTTP/HTTPS 协议访问的分布式存储服务。腾讯云 COS 的存储桶空间无容量上限,无需分区管理,适用于 CDN 数据分发、数据万象处理或大数据计算与分析的数据湖等多种场景。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档