Java默认的序列化机制非常简单,而且序列化后的对象不需要再次调用构造器重新生成,但是在实际中,我们可以会希望对象的某一部分不需要被序列化,或者说一个对象被还原之后,其内部的某些子对象需要重新创建,从而不必将该子对象序列化。 在这些情况下,我们可以考虑实现Externalizable接口从而代替Serializable接口来对序列化过程进行控制(后面我们会讲到一个更简单的方式,通过transient的方式)。

Externalizable接口extends Serializable接口,而且在其基础上增加了两个方法:writeExternal()和readExternal()。这两个方法会在序列化和反序列化还原的过程中被自动调用,以便执行一些特殊的操作。

package java.io;

import java.io.ObjectOutput;
import java.io.ObjectInput; public interface Externalizable extends java.io.Serializable { void writeExternal(ObjectOutput out) throws IOException; void readExternal(ObjectInput in) throws IOException, ClassNotFoundException;
}

下面这段代码示范了如何完整的保存和恢复一个Externalizable对象

package test.serializable;

import java.io.Externalizable;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectOutput; public class Blip implements Externalizable { private int i ; private String s;//没有初始化 public Blip() {
//默认构造函数必须有,而且必须是public
System.out.println("Blip默认构造函数");
} public Blip(String s ,int i) {
//s,i只是在带参数的构造函数中进行初始化。
System.out.println("Blip带参数构造函数");
this.s = s;
this.i = i;
} public String toString() {
return s + i ;
} @Override
public void readExternal(ObjectInput in) throws IOException,
ClassNotFoundException {
System.out.println("调用readExternal()方法");
s = (String)in.readObject();//在反序列化时,需要初始化s和i,否则只是调用默认构造函数,得不到s和i的值
i = in.readInt();
} @Override
public void writeExternal(ObjectOutput out) throws IOException {
System.out.println("调用writeExternal()方法");
out.writeObject(s); //如果我们不将s和i的值写入的话,那么在反序列化的时候,就不会得到这些值。
out.writeInt(i);
} }
package test.serializable;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream; public class ExternalizableTest { /**
* @param args
* @throws IOException
* @throws ClassNotFoundException
*/
public static void main(String[] args) throws IOException, ClassNotFoundException {
System.out.println("序列化之前");
Blip b = new Blip("This String is " , 47);
System.out.println(b); System.out.println("序列化操作,writeObject");
ByteArrayOutputStream out = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(out);
oos.writeObject(b);
System.out.println("反序列化之后,readObject");
ByteArrayInputStream in = new ByteArrayInputStream(out.toByteArray());
ObjectInputStream ois = new ObjectInputStream(in);
Blip bb = (Blip)ois.readObject();
System.out.println(bb);
}
}

运行结果如下所示:

序列化之前
Blip带参数构造函数
This String is 47
序列化操作,writeObject
调用writeExternal()方法
反序列化之后,readObject
Blip默认构造函数
调用readExternal()方法
This String is 47

分析结果:

在Blip类中,字段s和i只在第二个构造器中初始化,而不是在默认的构造器其中初始化的,每次writeObject时,都会调用WriteExtenal()方法,而在WriteExtenal()方法中我们需要将当前对象的值写入到流中;而每次readObject()时,调用的是默认的构造函数,如果我们不在 readExternal()方法中初始化s和i,那么s就会为null,而i就会为0。

下面分几种情况讨论:

 1) 如果我们只修改writeExternal()方法如下:

@Override
public void writeExternal(ObjectOutput out) throws IOException {
System.out.println("调用writeExternal()方法");
// out.writeObject(s);
// out.writeInt(i);
}

那么运行的结果为:

序列化之前
Blip带参数构造函数
This String is 47
序列化操作,writeObject
调用writeExternal()方法
反序列化之后,readObject
Blip默认构造函数
调用readExternal()方法
Exception in thread "main" java.io.OptionalDataException
at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1349)
at java.io.ObjectInputStream.readObject(ObjectInputStream.java:351)
at test.serializable.Blip.readExternal(Blip.java:34)
at java.io.ObjectInputStream.readExternalData(ObjectInputStream.java:1792)
at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:1751)
at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1329)
at java.io.ObjectInputStream.readObject(ObjectInputStream.java:351)
at test.serializable.ExternalizableTest.main(ExternalizableTest.java:28)

原因是因为,我们在ObjectOutPutStream中没有writeObject,而在ObjectInputStream中readObject导致的

 2)如果我们修改writeExternal()方法如下:

@Override
public void writeExternal(ObjectOutput out) throws IOException {
System.out.println("调用writeExternal()方法");
out.writeObject("自己定义的");
out.writeInt(250);
}

那么运行的结果为:

序列化之前
Blip带参数构造函数
This String is 47
序列化操作,writeObject
调用writeExternal()方法
反序列化之后,readObject
Blip默认构造函数
调用readExternal()方法
自己定义的250

看见没,反序列化后得到的s和i是我们在writeExternal()中自定义的数据

  3) 如果我们只是修改readExternal()方法

    @Override
public void readExternal(ObjectInput in) throws IOException,
ClassNotFoundException {
System.out.println("调用readExternal()方法");
// s = (String)in.readObject();
// i = in.readInt();
}

那么运行的结果为:

序列化之前
Blip带参数构造函数
This String is 47
序列化操作,writeObject
调用writeExternal()方法
反序列化之后,readObject
Blip默认构造函数
调用readExternal()方法
null0

看见没?最后一行打印的是null0,说明没有对s和i进行初始化。

4)如果我们删除Blip的默认构造函数,或者将其权限不设置为public

//    public Blip() {
// //默认构造函数必须有,而且必须是public
// System.out.println("Blip默认构造函数");
// }
// or
Blip() {
//默认构造函数必须有,而且必须是public
System.out.println("Blip默认构造函数");
}

运行结果如下:

序列化之前
Blip带参数构造函数
This String is 47
序列化操作,writeObject
调用writeExternal()方法
反序列化之后,readObject
Exception in thread "main" java.io.InvalidClassException: test.serializable.Blip; test.serializable.Blip; no valid constructor
at java.io.ObjectStreamClass.checkDeserialize(ObjectStreamClass.java:713)
at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:1733)
at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1329)
at java.io.ObjectInputStream.readObject(ObjectInputStream.java:351)
at test.serializable.ExternalizableTest.main(ExternalizableTest.java:28)
Caused by: java.io.InvalidClassException: test.serializable.Blip; no valid constructor
at java.io.ObjectStreamClass.<init>(ObjectStreamClass.java:471)
at java.io.ObjectStreamClass.lookup(ObjectStreamClass.java:310)
at java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1106)
at java.io.ObjectOutputStream.writeObject(ObjectOutputStream.java:326)
at test.serializable.ExternalizableTest.main(ExternalizableTest.java:24)

在反序列化时,会出现无效的构造函数这个错误,可见必须有权限为public的默认的构造器(如果有非默认的带参数的构造函数,那么必须显示的写出默认的构造函数,如果没有非默认的构造函数,那么默认构造函数可以不显示的写出来),才能使Externalizable对象产生正确的行为。

总结Externalizable对象的用法:

与Serizable对象不同,使用Externalizabled,就意味着没有任何东西可以自动序列化, 为了正常的运行,我们需要在writeExtenal()方法中将自对象的重要信息写入,从而手动的完成序列化。对于一个Externalizabled对象,对象的默认构造函数都会被调用(包括哪些在定义时已经初始化的字段),然后调用readExternal(),在此方法中必须手动的恢复数据。

JAVA 对象序列化(二)——Externalizable的更多相关文章

  1. 理解Java对象序列化(二)

    关于Java序列化的文章早已是汗牛充栋了,本文是对我个人过往学习,理解及应用Java序列化的一个总结.此文内容涉及Java序列化的基本原理,以及多种方法对序列化形式进行定制.在撰写本文时,既参考了Th ...

  2. java 对象序列化与反序列化

    Java序列化与反序列化是什么? 为什么需要序列化与反序列化? 如何实现Java序列化与反序列化? 本文围绕这些问题进行了探讨. 1.Java序列化与反序列化  Java序列化是指把Java对象转换为 ...

  3. Java 对象序列化机制详解

    对象序列化的目标:将对象保存到磁盘中,或允许在网络中直接传输对象. 对象序列化机制允许把内存中的Java对象转换成平台无关的二进制流,从而允许把这种二进制流持久的保存在磁盘上,通过网络将这种二进制流传 ...

  4. Java对象序列化剖析

    对象序列化的目的 1)希望将Java对象持久化在文件中 2)将Java对象用于网络传输 实现方式 如果希望一个类的对象可以被序列化/反序列化,那该类必须实现java.io.Serializable接口 ...

  5. 理解Java对象序列化

    http://www.blogjava.net/jiangshachina/archive/2012/02/13/369898.html 1. 什么是Java对象序列化 Java平台允许我们在内存中创 ...

  6. Java对象序列化入门

      Java对象序列化入门 关于Java序列化的文章早已是汗牛充栋了,本文是对我个人过往学习,理解及应用Java序列化的一个总结.此文内容涉及Java序列化的基本原理,以及多种方法对序列化形式进行定制 ...

  7. Java 对象序列化和反序列化

         之前的文章中我们介绍过有关字节流字符流的使用,当时我们对于将一个对象输出到流中的操作,使用DataOutputStream流将该对象中的每个属性值逐个输出到流中,读出时相反.在我们看来这种行 ...

  8. Java对象序列化全面总结

    前言 Java允许我们在内存中创建可复用的Java对象,但一般情况下,这些对象的生命周期不会比JVM的生命周期更长.但在现实应用中,可能要求在JVM停止运行之后能够保存(持久化)指定的对象,并在将来重 ...

  9. 转:Java对象序列化

    Java对象序列化 当两个进程在进行远程通信时,彼此可以发送各种类型的数据.无论是何种类型的数据,都会以二进制序列的形式在网络上传送.发送方需要把这个Java对象转换为字节序列,才能在网络上传送:接收 ...

  10. 深入理解Java对象序列化

    关于Java序列化的文章早已是汗牛充栋了,本文是对我个人过往学习,理解及应用Java序列化的一个总结.此文内容涉及Java序列化的基本原理,以及多种方法对序列化形式进行定制.在撰写本文时,既参考了Th ...

随机推荐

  1. 判断线段之间的关系(D - Intersecting Lines POJ - 1269 )

    题目链接:https://vjudge.net/contest/276358#problem/D 题目大意:每一次给你两条直线,然后问你这两条直线的关系(平行,共线,相交(输出交点)). 具体思路:先 ...

  2. VS2017创建类库项目后添加不了WPF资源字典

    第一步: 先找到你需要添加的库类工程文件,位置如下: 第二步: 使用记事本文件打开,找到图片的位置,把三行代码粘贴进去,保存文件.重新打开项目: 三行代码如下: <ProjectTypeGuid ...

  3. 大数据系列之数据仓库Hive安装

    Hive系列博文,持续更新~~~ 大数据系列之数据仓库Hive原理 大数据系列之数据仓库Hive安装 大数据系列之数据仓库Hive中分区Partition如何使用 大数据系列之数据仓库Hive命令使用 ...

  4. 如何让Ubuntu 12.04 LTS更炫更具吸引力

    Ubuntu 12.04 LTS震撼发布   适逢七周岁生日之际,Ubuntu正式推出了第四个LTS长期支持版本,开发代号Precise Pangolin的Ubuntu 12.04在2012年4月26 ...

  5. linux pwd指令C实现

    linux pwd指令C实现 研究实现pwd所需的系统调用 我们可以通过man命令和grep命令来获取我们所需要的系统调用函数信息 man chdir Linux pwd命令用于显示工作目录. 执行p ...

  6. 关于x509、crt、cer、key、csr、pem、der、ssl、tls 、openssl等

    关于x509.crt.cer.key.csr.pem.der.ssl.tls .openssl等 TLS:传输层安全协议 Transport Layer Security的缩写 TLS是传输层安全协议 ...

  7. shell学习(一)

    一.免密交互 --stdin 1.从标准输入读取字符串 如:passwd   --stdin   heruguo246 [root@localhost mnt]#    ---输入了修改heruigu ...

  8. VMware虚拟机三种联网方法及原理

    VMware虚拟机三种联网方法及原理   一.Brigde——桥接:默认使用VMnet0   1.原理:   Bridge 桥"就是一个主机,这个机器拥有两块网卡,分别处于两个局域网中,同时 ...

  9. Django admin 产生'WSGIRequest' object has no attribute 'user'的错误

    Django admin 产生'WSGIRequest' object has no attribute 'user'的错误 django 版本1.8.升级到django2.0,   Django服务 ...

  10. python常用库安装网址

    python常用库安装网址如下: http://pypi.python.org/pypi