Java 对象序列化详解以及实例实现和源码下载

Java中的序列化机制有两种实现方式:

一种是实现Serializable接口

另一种是实现Externalizable接口

区别:

实现Serializable接口

1 系统自动储存必要的信息

2 Java内建支持,易于实现,只需实现该接口即可,无须任何代码支持

3 性能略差

实现Externalizable接口

1 程序员决定存储哪些信息

2 仅仅提供两个空方法,实现该接口必须为两个空方法提供实现

3 性能略好

由于实现Externalizable接口导致了编程复杂度的增加,所以大部分时候采用实现Serializable接口方式实现序列化。

下面给出一个实例

public class PersonBean implements Serializable {
    private String name;
    private int age;
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public int getAge() {
        return age;
    }
    public void setAge(int age) {
        this.age = age;
    }
    @Override
    public String toString() {
        return "name="+this.name + ",age=" + this.age;
    }
}

这是一个PersonBean类,就这么简单已经实现了PersonBean类的序列化。

下面就可以使用ObjectInputStream、ObjectOutputStream类进行对象的写入和读取操作了。

代码如下:

public static void main(String[] args) {
        PersonBean personBean1 = new PersonBean();
        personBean1.setName("long");
        personBean1.setAge(20);
        PersonBean personBean2 = new PersonBean();
        personBean2.setName("fei");
        personBean2.setAge(25);
        ObjectOutputStream objectOutputStream = null;
        ObjectInputStream objectInputStream = null;
        String path = "D:\\Program Files (x86)\\ADT\\workspace\\JavaIO\\demoTest.txt";
        try {
            FileOutputStream fileOutputStream = new FileOutputStream(path);
            FileInputStream fileInputStream = new FileInputStream(path);
            objectOutputStream = new ObjectOutputStream(fileOutputStream);
            objectOutputStream.writeObject(personBean1);
            objectOutputStream.writeObject(personBean2);

            objectInputStream = new ObjectInputStream(fileInputStream);
            PersonBean personBean3 = (PersonBean)objectInputStream.readObject();
            PersonBean personBean4 = (PersonBean) objectInputStream.readObject();
            System.out.println(personBean3.toString());
            System.out.println(personBean4.toString());
        } catch (IOException | ClassNotFoundException e) {
            e.printStackTrace();
        }finally{
            if (objectOutputStream != null) {
                try {
                    objectOutputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

结果图:

Java中允许自定义序列化,提供了一个修饰符transient,该修饰符只能修饰Field字段,不能修饰方法和类,该修饰符达到的效果是把被修饰的Field完全隔离在序列化机制之外。这样导致在反序列化回复java对象时无法取得该Field值。

现在把PeronBean类的age字段使用该修饰符修饰,其他代码保持不变如下:

public class PersonBean implements Serializable {
    private String name;
    private transient int age;
    ..............

然后再运行程序Demo,结果如下:

看到了结果大家应该明白了该修饰符的意义了吧。

自定义序列化机制还没有这么简单,还可以更强大。

在序列化和反序列化过程中需要特殊处理的类提供如下特殊签名的方法,这些特殊的方法用以实现自定义序列化

private void writeObject(java.io.ObjectOutputStream) throws IOException
private void readObject(java.io.ObjectInputStream) throws IOException,ClassNotFoundException
private void readObjectNoData() throws ObjectStreamException

writeObject方法负责写入特定累的实例状态,readObject方法可以回复它

当序列流不完整时,readObjectNoData方法可以用来正确地初始化反序列化的对象:例如,接收方使用的反序列化类的版本不用于发送方,或者接收方版本扩展的类不是放松方版本扩展的类,或者序列化流被篡改时,系统都会调用readObjectNoData方法类初始化反序列化的对象。

下面重写PersonBean类,自定义序列化,代码的如下:

public class PersonBean implements Serializable {
    private String name;
    private transient int age;
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public int getAge() {
        return age;
    }
    public void setAge(int age) {
        this.age = age;
    }

    private void writeObject(ObjectOutputStream out) throws IOException{
        out.writeObject(new StringBuffer(name).reverse());
        out.writeInt(age);
    }

    private void readObject(ObjectInputStream in) throws ClassNotFoundException, IOException{
        this.name = ((StringBuffer)in.readObject()).toString();
        this.age = in.readInt();
    }
    @Override
    public String toString() {
        return "name="+this.name + ",age=" + this.age;
    }
}

writeObject方法,把名字反转后写入,readObject方法直接读取反转后的名字即可。Demo类代码不变,结果如图:

从结果可以看出自定义序列化已经实现,并且writeObject、readObject也已经被调用了。

细心的读者可能发现,PersonBean类中int age使用了transient修饰符修饰了,但是读取之后的结果age不等于0??前面讲过,使用transient修饰符修饰的field是完全隔离的。但是这里需要注意的是,这里的序列化是自定义的,自定义的过程中并没有对该字段进行处理,当然transient修饰符在这里并不起作用了。。。

还有一种更加彻底的序列化自定义机制

ANY-ACCESS-MODIFIER Object writeReplace() throws ObjectStreamException

该方法前面的ANY-ACCESS-MODIFIER说明该方法可以用于private protected package-private等访问权限,其子类可能获得该方法。

下面重写PersonBean类,如下:

public class PersonBean implements Serializable {
    private String name;
    private transient int age;
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public int getAge() {
        return age;
    }
    public void setAge(int age) {
        this.age = age;
    }

    private void writeObject(ObjectOutputStream out) throws IOException{
        out.writeObject(new StringBuffer(name).reverse());
        out.writeInt(age);
    }

    private void readObject(ObjectInputStream in) throws ClassNotFoundException, IOException{
        this.name = ((StringBuffer)in.readObject()).toString();
        this.age = in.readInt();
    }
    @Override
    public String toString() {
        return "name="+this.name + ",age=" + this.age;
    }

    private Object writeReplace() throws ObjectStreamException{
        ArrayList<Object> arrayList = new ArrayList<>();
        arrayList.add(this.name);
        arrayList.add(this.age);
        return arrayList;
    }
}

Java的序列化机制保证在序列化某个对象之前,先调用该对象的writeReplace方法,如果该方法返回一个Java对象,则系统转为序列化另一个Java对象。上面代码中序列化personbean对象,但是writeReplace方法返回ArrayList对象,所以序列化保存的是ArrayList对象。那么读取保存的序列化对象时获取的当然也是ArrayList对象,所以Demo类代码改为如下:

public class ObjectDemo {
    public static void main(String[] args) {
        PersonBean personBean1 = new PersonBean();
        personBean1.setName("long");
        personBean1.setAge(20);
        PersonBean personBean2 = new PersonBean();
        personBean2.setName("fei");
        personBean2.setAge(25);
        ObjectOutputStream objectOutputStream = null;
        ObjectInputStream objectInputStream = null;
        String path = "D:\\Program Files (x86)\\ADT\\workspace\\JavaIO\\demoTest.txt";
        try {
            FileOutputStream fileOutputStream = new FileOutputStream(path);
            FileInputStream fileInputStream = new FileInputStream(path);
            objectOutputStream = new ObjectOutputStream(fileOutputStream);
            objectOutputStream.writeObject(personBean1);
            objectOutputStream.writeObject(personBean2);

            objectInputStream = new ObjectInputStream(fileInputStream);
//          PersonBean personBean3 = (PersonBean)objectInputStream.readObject();
//          PersonBean personBean4 = (PersonBean) objectInputStream.readObject();
            ArrayList<Object> personBean3 = (ArrayList<Object>)objectInputStream.readObject();
            ArrayList<Object> personBean4 = (ArrayList<Object>) objectInputStream.readObject();
            System.out.println(personBean3.toString());
            System.out.println(personBean4.toString());
        } catch (IOException | ClassNotFoundException e) {
            e.printStackTrace();
        }finally{
            if (objectOutputStream != null) {
                try {
                    objectOutputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

结果:

  • 与writeReplace方法相对的是

ANY-ACCESS-MODIFIER Object readResolve() throws ObjectStreamException

该方法可以实现保护性复制整个对象。该方法在序列化单例类、枚举类时尤其有用。大家想想为什么??

原因在于,单例类和枚举类希望反序列化之后返回的还是原来的对象,但是在不实用该方法的情况下,反序列化之后放回的是原来的对象的克隆!!不是原来的对象!该方法就可以解决这个问题,返回原来序列化的同一个对象!!是不是很强大!

  • 另一种实现自定义序列化的机制是使用Externalizable接口,实现该接口需要实现两个方法

    void readExternal(ObjectInput in)

    void writeExternal(ObjectOutput out)

    这两个方法和签名使用readObject writeObject方法非常相似,只是Externalizable接口强制使用自定义序列化!除了重写这两个方法之外,其他操作都一样,也就不举例子了。。

  • 关于序列化机制 需要注意的几点:

    1、对象的类名、Field都会被序列化,方法、static field、transient field都不会被序列化

    2、实现Serializable接口使用transient修饰符修饰field可以不被序列化,虽然static 修饰符可以达到同样的效果,但是static transient不能混用。

    3、反序列化时必须有序列化对象的class文件!!!

    4、当通过文件、网络来读取序列化后的对象时,必须按实际写入的顺序读取。

  • Java序列化机制允许为序列化类提供一个private static final 的serialVersionUID值,该值用于标识该类的序列化版本。如果一个类升级了,只要它的serialVersionUID Field值保持不变,序列化机制也会把他们当成一个序列化版本。

    为了在反序列化时确保序列化版本的兼容性,最好在每个要序列化的类中加入private static final long serialVersionUID这个field,它的具体值可以自己定义。

    不显式指定serialVersionUIR Field值的一个坏处,serialVersionUID值有JVM根据类的相关信息计算,而修改后的类的计算结果与修改前的类的计算结果往往不同,从而造成对象的反序列化因为类版本的不兼容而失败。

    另外一个坏处是,不利于程序在不用的JVM之间移植。因为不同的编译器计算该值的计算策略可能不同,从而造成虽然累完全没有改变,但是因为JVM不同,也会出现序列化版本不兼容而无法正确反序列化的现象。

    有这么集中情况需要讨论:

    1、如果修改类是仅仅修改了方法,则反序列化不受影响。无须指定serialVersionUID值

    2、如果修改类时仅仅修改了静态Field或瞬态Field,则反序列化不受影响。无须指定serialVersionUID值。

    3、如果修改类时修改了非静态的Field 非瞬态Field,则可能导致序列化版本不兼容。如果对象流中的对象和新类中包含同名的Field,而类型不同,则反序列化失败,类定义应该更新serialVersionUID值。如果对象流中包含比新类中更多的Field,则多出的Field值被忽略,序列化版本可以兼容,类定义可以不更新serialVersionUID值。如果新类比对象流中的对象包含更多的Field,则序列化版本也可以兼容,不用指定serialVersionUID值,但反序列化得到的新类的对象中多出的Field值为null或者0。

源码下载

时间: 2024-08-02 00:59:04

Java 对象序列化详解以及实例实现和源码下载的相关文章

redis存储对象与对象序列化详解

redis主要存储类型最常用的五种数据类型: String Hash List Set Sorted set redis存储对象序列化和反序列化 首先来了解一下为什么要实现序列化 为什么要实现序列化接口 当一个类实现了Serializable接口(该接口仅为标记接口,不包含任何方法定义),表示该类可以序列化.序列化的目的是将一个实现了Serializable接口的对象转换成一个字节序列,可以. 把该字节序列保存起来(例如:保存在一个文件里),以后可以随时将该字节序列恢复为原来的对象.甚至可以将该

Java对象初始化详解

出处:http://blog.jobbole.com/23939/ 在Java中,一个对象在可以被使用之前必须要被正确地初始化,这一点是Java规范规定的.本文试图对Java如何执行对象的初始化做一个详细深 入地介绍(与对象初始化相同,类在被加载之后也是需要初始化的,本文在最后也会对类的初始化进行介绍,相对于对象初始化来说,类的初始化要相对简单一 些). 1.Java对象何时被初始化 Java对象在其被创建时初始化,在Java代码中,有两种行为可以引起对象的创建.其中比较直观的一种,也就是通常所

Java对象初始化详解(转)

在Java中,一个对象在可以被使用之前必须要被正确地初始化,这一点是Java规范规定的.本文试图对Java如何执行对象的初始化做一个详细深入地介绍(与对象初始化相同,类在被加载之后也是需要初始化的,本文在最后也会对类的初始化进行介绍,相对于对象初始化来说,类的初始化要相对简单一些). 1.Java对象何时被初始化 Java对象在其被创建时初始化,在Java代码中,有两种行为可以引起对象的创建.其中比较直观的一种,也就是通常所说的显式对象创建,就是通过new关键字来调用一个类的构造函数,通过构造函

JavaScript之对象序列化详解

一.什么是对象序列化? 对象序列化是指将对象的状态转换为字符串(来自我这菜鸟的理解,好像有些书上也是这么说的,浅显易懂!): 序列化(Serialization)是将对象的状态信息转换为可以存储或传输的形式的过程(来自“百度百科—序列化“,学术性强,略显高端): 二.为什么会有对象序列化? 世间万物,都有其存在的原因.为什么会有对象序列化呢?因为程序猿们需要它.既然是对象序列化,那我们就先从一个对象说起: var obj = {x:1, y:2}; 当这句代码运行时,对象obj的内容会存储在一块

Java学习1:图解Java内存分析详解(实例)

首先需要明白以下几点: 栈空间(stack),连续的存储空间,遵循后进先出的原则,用于存放局部变量. 堆空间(heap),不连续的空间,用于存放new出的对象,或者说是类的实例. 方法区(method),方法区在堆空间内,用于存放①类的代码信息:②静态变量和方法:③常量池(字符串敞亮等,具有共享机制). Java中除了基本数据类型,其他的均是引用类型,包括类.数组等等. 数据类型的默认值 基本数据类型默认值: 数值型:0 浮点型:0.0 布尔型:false 字符型:\u0000 引用类型:nul

Java线程池详解及实例

前言 多线程的异步执行方式,虽然能够最大限度发挥多核计算机的计算能力,但是如果不加控制,反而会对系统造成负担.线程本身也要占用内存空间,大量的线程会占用内存资源并且可能会导致Out of Memory.即便没有这样的情况,大量的线程回收也会给GC带来很大的压力. 为了避免重复的创建线程,线程池的出现可以让线程进行复用.通俗点讲,当有工作来,就会向线程池拿一个线程,当工作完成后,并不是直接关闭线程,而是将这个线程归还给线程池供其他任务使用. 接下来从总体到细致的方式,来共同探讨线程池. 总体的架构

Java学习-007-Log4J 日志记录配置文件详解及实例源代码

此文主要讲述在初学 Java 时,常用的 Log4J 日志记录配置文件详解及实例源代码整理.希望能对初学 Java 编程的亲们有所帮助.若有不足之处,敬请大神指正,不胜感激!源代码测试通过日期为:2015-1-30 13:54:02,请知悉. 所需的 jar 包下载链接为:http://yunpan.cn/cKE56sxqtQCfP  访问密码 63d8 有关 Log4J 日志文件中日志级别及文件配置的详细情况,在 Log4J 的配置文件(xml.properties)中有详细的介绍,敬请参阅!

每天进步一点点-实例为导学-一个java对象序列化的例子

序列化和反序列化例子 如果我们想要序列化一个对象, (对象 转 序列)首先要创建某些OutputStream(如FileOutputStream.ByteArrayOutputStream等),然后将这些OutputStream封装在一个ObjectOutputStream中.这时候,只需要调用writeObject()方法就可以将对象序列化,并将其发送给OutputStream(记住:对象的序列化是基于字节(1字节8位)的,不能使用Reader和Writer等基于字符的层次结构).而反序列的过

Rxjava2 Observable的辅助操作详解及实例(一)

目录 简要: 1. Delay 2. Do 3. SubscribeOn 4. ObserverOn 5. Serialize 6. Materialize 7. Dematerialize 接续: 简要: 需求了解: Rxjava中有一些方便的辅助操作符,来更方便我们的函数式的编程.比如延迟.定时.指定操作的监听.数据类型转换等一系列的操作. 下面列出了一些用于Observable的辅助操作符: Delay:延时发射Observable的结果. Do:注册一个动作作为原始Observable生