为什么很多类甚者底层源码要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 对象并返回。
        return null;
    }

    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。

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;

public class Book implements Serializable {
    // private static final long serialVersionUID = 5805574648076667828L;
    String name = "marsXTU";

    // int vale = 7;

    // 序列化写入文件
    public void save() throws IOException {
        FileOutputStream f = new FileOutputStream("d://book.txt");
        ObjectOutputStream oos = new ObjectOutputStream(f);
        oos.writeObject(this);
        oos.close();
    }

    // 反序列化从文件读取
    public void read() throws IOException, ClassNotFoundException {
        FileInputStream f = new FileInputStream("d://book.txt");
        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) {
            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。

为什么很多类甚者底层源码要implements Serializable ?的更多相关文章

  1. Android开发之漫漫长途 Ⅵ——图解Android事件分发机制(深入底层源码)

    该文章是一个系列文章,是本人在Android开发的漫漫长途上的一点感想和记录,我会尽量按照先易后难的顺序进行编写该系列.该系列引用了<Android开发艺术探索>以及<深入理解And ...

  2. Java泛型底层源码解析-ArrayList,LinkedList,HashSet和HashMap

    声明:以下源代码使用的都是基于JDK1.8_112版本 1. ArrayList源码解析 <1. 集合中存放的依然是对象的引用而不是对象本身,且无法放置原生数据类型,我们需要使用原生数据类型的包 ...

  3. List-LinkedList、set集合基础增强底层源码分析

    List-LinkedList 作者 : Stanley 罗昊 [转载请注明出处和署名,谢谢!] 继上一章继续讲解,上章内容: List-ArreyLlist集合基础增强底层源码分析:https:// ...

  4. List-ArrayList集合基础增强底层源码分析

    List集合基础增强底层源码分析 作者:Stanley 罗昊 [转载请注明出处和署名,谢谢!] 集合分为三个系列,分别为:List.set.map List系列 特点:元素有序可重复 有序指的是元素的 ...

  5. 从底层源码浅析Mybatis的SqlSessionFactory初始化过程

    目录 搭建源码环境 POM依赖 测试SQL Mybatis全局配置文件 UserMapper接口 UserMapper配置 User实体 Main方法 快速进入Debug跟踪 源码分析准备 源码分析 ...

  6. BAT资深工程师 由浅入深分析 Tp5&Tp6底层源码 - 分享

    BAT资深工程师由浅入深分析Tp5&Tp6底层源码 第1章 课程简介 本章主要让大家知道本套课程的主线, 导学内容,如何学习源码等,看完本章要让小伙伴觉得这个是必须要掌握的,并且对加薪有很大的 ...

  7. BAT资深工程师由浅入深分析Tp5&Tp6底层源码☆

    第1章 课程简介 本章主要让大家知道本套课程的主线, 导学内容,如何学习源码等,看完本章要让小伙伴觉得这个是必须要掌握的,并且对加薪有很大的帮助. 第2章 [TP5灵魂]自动加载Loader 深度分析 ...

  8. 总结HashSet以及分析部分底层源码

    总结HashSet以及分析部分底层源码 1. HashSet继承的抽象类和实现的接口 继承的抽象类:AbstractSet 实现了Set接口 实现了Cloneable接口 实现了Serializabl ...

  9. LInkedList总结及部分底层源码分析

    LInkedList总结及部分底层源码分析 1. LinkedList的实现与继承关系 继承:AbstractSequentialList 抽象类 实现:List 接口 实现:Deque 接口 实现: ...

随机推荐

  1. Postgresql 创建SEQUENCE,Springboot中使用KeyHolder

    项目中使用到JdbcTemplate中的KeyHolder,代码如下: String sql = "insert into web_users(username, password, pho ...

  2. poj 2449 Remmarguts' Date 第k短路 (最短路变形)

    Remmarguts' Date Time Limit: 4000MS   Memory Limit: 65536K Total Submissions: 33606   Accepted: 9116 ...

  3. CSS之绝对定位

    w3school定义: 绝对定位的元素的位置相对于最近的已定位祖先元素(这里的已定位指的是绝对定位或者相对定位),如果元素没有已定位的祖先元素,那么它的位置相对于最初的包含块. 对于定位的主要问题是要 ...

  4. 剖析Vue原理&实现双向绑定MVVM

    转自:http://www.w3cmark.com/2016/496.html 本文能帮你做什么? 1.了解vue的双向数据绑定原理以及核心代码模块 2.缓解好奇心的同时了解如何实现双向绑定 为了便于 ...

  5. ExecutorService

    接口 java.util.concurrent.ExecutorService 表述了异步执行的机制,并且可以让任务在后台执行.壹個 ExecutorService 实例因此特别像壹個线程池.事实上, ...

  6. IDEA中Git的使用

    工作中多人使用版本控制软件协作开发,常见的应用场景归纳如下: 假设小组中有两个人,组长小张,组员小袁 场景一:小张创建项目并提交到远程Git仓库 场景二:小袁从远程Git仓库上获取项目源码 场景三:小 ...

  7. Bootstrap3 排版-改变大小写

    通过这几个类可以改变文本的大小写. <p class="text-lowercase">Lowercased text.</p> <p class=& ...

  8. Markdown对应Yelee主题语法

    概述 这里说的是Yelee主题的语法和原生语法是有些区别的:更多的基础语法可以到Cmd Markdown上面去查看:但是我觉得都会各有不同吧 注意这里说的不是真正意义上的Markdown语法 标题 一 ...

  9. 全废话SQL Server统计信息(2)——统计信息基础

    接上文:http://blog.csdn.net/dba_huangzj/article/details/52835958 我想在大地上画满窗子,让所有习惯黑暗的眼睛都习惯光明--顾城<我是一个 ...

  10. 重写方法的利器-super

    重写方法的利器-super class ilist(list): def __init__(self,dft=None,r=list()): super(ilist, self).__init__(r ...