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

来源:互联网 发布:js input只读属性 编辑:程序博客网 时间:2024/06/05 11:14

原文出处:http://blog.csdn.net/jinxinliu1/article/details/52711569

在碰到异常类RuntimeException时,发现Throwable实现了 Serializable,还有我们平进的javabean一般也要实现Serializable,不明白为什么?做个小总结如下:

[java] view plain copy
 
 
  1. public class Throwable implements Serializable {  
  2.     /** use serialVersionUID from JDK 1.0.2 for interoperability */  
  3.     private static final long serialVersionUID = -3042686055658047285L;  
  4. }  

  • 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类对象也会被序列化,这可能会造成用户信息的泄露。


[java] view plain copy
 
 
  1. private static class OrderReplace implements Serializable{  
  2.     private static final long serialVersionUID=47832438;  
  3.     private String orderId;  
  4.     public OrderReplace(Order order){  
  5.         this.orderId = order.getId();  
  6.     }  
  7.   
  8.     private Object readResolve(){  
  9.         //根据orderId查找Order 对象并返回。  
  10.         return null;  
  11.     }  
  12.   
  13.     public Object writeReplace(){  
  14.         return new OrderReplace(this);  
  15.     }  
  16. }  


这个替换对象类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。


[java] view plain copy
 
 
  1. import java.io.FileInputStream;  
  2. import java.io.FileOutputStream;  
  3. import java.io.IOException;  
  4. import java.io.ObjectInputStream;  
  5. import java.io.ObjectOutputStream;  
  6. import java.io.Serializable;  
  7.   
  8. public class Book implements Serializable {  
  9.     // private static final long serialVersionUID = 5805574648076667828L;  
  10.     String name = "marsXTU";  
  11.   
  12.     // int vale = 7;  
  13.   
  14.     // 序列化写入文件  
  15.     public void save() throws IOException {  
  16.         FileOutputStream f = new FileOutputStream("d://book.txt");  
  17.         ObjectOutputStream oos = new ObjectOutputStream(f);  
  18.         oos.writeObject(this);  
  19.         oos.close();  
  20.     }  
  21.   
  22.     // 反序列化从文件读取  
  23.     public void read() throws IOException, ClassNotFoundException {  
  24.         FileInputStream f = new FileInputStream("d://book.txt");  
  25.         ObjectInputStream ois = new ObjectInputStream(f);  
  26.         Book b = (Book) ois.readObject();  
  27.         ois.close();  
  28.     }  
  29.   
  30.     public static void main(String[] args) {  
  31.         Book b = new Book();  
  32.         try {  
  33.             // 序列化  
  34.             b.save();  
  35.             // 读取  
  36.             b.read();  
  37.         } catch (Exception e) {  
  38.             e.printStackTrace();  
  39.         }  
  40.     }  
  41. }  

  • 如果增加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。

0 0
原创粉丝点击