java io系列06之 序列化总结(Serializable 和 Externalizable)

本文摘抄至:skywang12345

摘要:介绍了序列化的两种方式Serializable和Externalizable。和在使用 Serializable序列化时 使用 static、transient的情况。使用 Externalizable 需要无参构造、需要重写 writeExternal和readExternal才会对对象的进行保存。但是这两个方法是不安全。

本章,我们对序列化进行深入的学习和探讨。学习内容,包括序列化的作用、用途、用法,以及对实现序列化的2种方式Serializable .和 Externalizable
的深入研究。

1. 序列化是的作用和用途


序列化,就是为了 保存对象的状态;而与之对应的反序列化,则可以 把保存的对象状态再读出来。
简言之: 序列化/反序列化,是Java提供一种专门用于的保存/恢复对象状态的机制。

一般在以下几种情况下,我们可能会用到序列化:

  1. 当你想把的内存中的对象状态保存到一个文件中或者数据库中时候;
  2. 当你想用套接字在网络上传送对象的时候;
  3. 当你想通过RMI传输对象的时候。

2. Serializable 的演示程序


演示程序1

下面,我们先通过一则简单示例来查看序列化的用法。
源码如下(SerialTest1.java):

public class SerialTest1 {

    private static final String TMP_FILE = ".serialtest1.txt";

    public static void main(String[] args) {

        // 将“对象”通过序列化保存
        testWrite();
        // 将序列化的“对象”读出来
        testRead();
    }

    /**将 Box对象通过序列化,保存到文件中*/
    private static void testWrite(){

         // 获取文件TMP_FILE对应的对象输出流。
         // ObjectOutputStream中,只能写入“基本数据”或“支持序列化的对象”
        try {
            ObjectOutputStream out = new  ObjectOutputStream(
                    new FileOutputStream(TMP_FILE));
            //创建 Box 对象,Box实现了 Serializable 序列化接口
            Box01 box = new Box01("desk", 80, 48);
            //将 box 对象写入对象输出流 out中,既相当将对象保存到文件 IMP_FILE中
            out.writeObject(box);
            //打印“Box对象”
            System.out.println("testWrite box :"+box);

            out.flush();
            out.close();

        } catch (Exception e) {
            e.printStackTrace();
        } 
    }

    /**从文件中读取出“序列化的Box的对象”*/
    private static void testRead(){

        try {
            //获取文件IMP_FILE 对应的对象输出流
            ObjectInputStream in = new ObjectInputStream(
                    new FileInputStream(TMP_FILE));
            //从对象输入流中,读取先前保存的对象
            Box01 box  =(Box01) in.readObject();
            //打印“Box对象”
            System.out.println("testRead box : "+box);


            in.close();

        } catch (Exception e) {
            e.printStackTrace();
        } 

    }
}

/**
 * Box类“支持序列化”。因为Box实现了Serializable接口。
 * 实际上,一个类只需要实现Serializable即可实现序列化,而不需要实现任何函数。
 * @author hu
 */
class Box01 implements Serializable{

    private String name;
    private int width;
    private int height;

    public Box01(String name, int width, int height) {
        this.name = name;
        this.width = width;
        this.height = height;
    }

    @Override
    public String toString() {
        return "Box [name=" + name + ", width=" + width + ", height=" + height + "]";
    }

}

运行结果:

testWrite box :Box [name=desk, width=80, height=48]
testRead box : Box [name=desk, width=80, height=48]

源码说明:

  1. 程序的作用很简单,就是演示:先将Box对象,通过对象输出流保存到文件中;之后,再通过对象输入流,将文件中保存的Box对象读取出来。
  2. Box类说明。Box是我们自定义的演示类,它被用于序列化的读写。Box实现了Serialable接口,因此它支持序列化操作;即,Box支持通过ObjectOutputStream去写入到输出流中,并且支持通过ObjectInputStream从输入流中读取出来。
  3. testWrite()函数说明。testWrite()的作用就是,新建一个Box对象,然后将该Box对象写入到文件中。
    1. 首先,新建文件TMP_FILE的文件输出流对象(即FileOutputStream对象),再创建该文件输出流的对象输出流(即ObjectOutputStream对象)。
    2. 然后,新建Box对象。
    3. 最后,通过out.writeObject(box) 将box写入到对象输出流中。实际上,相当于将box写入到文件TMP_FILE中。

  4. testRead()函数说明。testRead()的作用就是,从文件中读出Box对象。
    1. 首先,新建文件TMP_FILE的文件输入流对象(即FileInputStream对象),再创建该文件输入流的对象输入流(即ObjectInputStream对象)。
    2. 然后,通过in.readObject() 从对象输入流中读取出Box对象。实际上,相当于从文件TMP_FILE中读取Box对象。


通过上面的示例,我们知道:我们可以自定义类,让它支持序列化(即实现Serializable接口),从而能支持对象的保存/恢复。
若要支持序列化,除了“自定义实现Serializable接口的类”之外;java的“基本类型”和“java自带的实现了Serializable接口的类”,都支持序列化。我们通过下面的示例去查看一下。

演示程序2

源码如下(SerialTest2.java):

/**
 *  “基本类型” 和 “java自带的实现Serializable接口的类” 对序列化的支持
 * @author hu
 *
 */
public class SerialTest2 {

    private static final String TMP_FILE = ".serialabletest2.txt";

    public static void main(String[] args) {

        testWrite();
        testRead();
    }

    private static void testWrite(){


        try {
            ObjectOutputStream out = new ObjectOutputStream(
                    new FileOutputStream(TMP_FILE));

            //写入 boolean值
            out.writeBoolean(true);
            //写入 byte
            out.writeByte((byte)65);
            //写入 Char
            out.writeChar('a');
            //写入  int值
            out.writeInt(20161002);
            //写入 float
            out.writeFloat(3.14f);
            //写入  Double
            out.writeDouble(1.4141d);

            //写入 map对象
            HashMap map = new HashMap<>();
            map.put("one", "red");
            map.put("two", "green");
            map.put("three", "blue");
            out.writeObject(map);

            out.flush();
            out.close();

        } catch (Exception e) {
            e.printStackTrace();
        }
    }


    private static void testRead(){

        try {
            ObjectInputStream in = new ObjectInputStream(
                    new FileInputStream(TMP_FILE));
            System.out.printf("boolean:%b\n" , in.readBoolean());
            System.out.printf("byte:%d\n" , (in.readByte()&0xff));
            System.out.printf("char:%c\n" , in.readChar());
            System.out.printf("int:%d\n" , in.readInt());
            System.out.printf("float:%f\n" , in.readFloat());
            System.out.printf("double:%f\n" , in.readDouble());
            // 读取HashMap对象
            HashMap map = (HashMap) in.readObject();
            Iterator iter = map.entrySet().iterator();
            while (iter.hasNext()) {
                Map.Entry entry = (Map.Entry)iter.next();
                System.out.printf("%-6s -- %s\n" , entry.getKey(), entry.getValue());
            }

            in.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

运行结果:

boolean:true
byte:65
char:a
int:20161002
float:3.140000
double:1.414100
one – red
two – green
three – blue

源码说明:

  1. 程序的作用很简单,就是演示:先将“基本类型数据”和“HashMap对象”,通过对象输出流保存到文件中;之后,再通过对象输入流,将这些保存的数据读取出来。
  2. testWrite()函数说明。testWrite()的作用就是,先将“基本类型数据”和“HashMap对象”,通过对象输出流保存到文件中。
    1. 首先,新建文件TMP_FILE的文件输出流对象(即FileOutputStream对象),再创建该文件输出流的对象输出流(即ObjectOutputStream对象)。
    2. 然后,通过 writeBoolean(), writeByte(), … , writeDouble() 等一系列函数将“Boolean, byte, char, … , double等基本数据类型”写入到对象输出流中。实际上,相当于将这些内容写入到文件TMP_FILE中。
    3. 最后,新建HashMap对象map,并通过out.writeObject(map) 将map写入到对象输出流中。实际上,相当于map写入到文件TMP_FILE中。

  3. testRead()函数说明。testRead()的作用就是,从文件中读出testWrite()写入的对象。
    1. 首先,新建文件TMP_FILE的文件输入流对象(即FileInputStream对象),再创建该文件输入流的对象输入流(即ObjectInputStream对象)。
    2. 然后,通过in.readObject() 从对象输入流中读取出testWrite()对象。实际上,相当于从文件TMP_FILE中读取出这些对象。


在前面,我们提到过:若要支持序列化,除了“自定义实现Serializable接口的类”之外;java的“基本类型”和“java自带的实现了Serializable接口的类”,都支持序列化。为了验证这句话,我们看看HashMap是否实现了Serializable接口。
HashMap是java.util包中定义的类,它的接口声明如下:
public class HashMap<K,V> extends AbstractMap<K,V>
    implements Map<K,V>, Cloneable, Serializable {...}

至此,我们对序列化的认识已经比较深入了:即知道了“序列化的作用和用法”,也知道了“基本类型”、“java自带的支持Serializable接口的类”和“自定义实现Serializable接口的类”都能支持序列化。

我们在介绍序列化定义时,说过“序列化/反序列化,是专门用于的保存/恢复对象状态的机制”。
从中,我们知道:序列化/反序列化,只支持保存/恢复对象状态,即仅支持保存/恢复类的成员变量,但不支持保存类的成员方法!
但是,序列化是不是对类的所有的成员变量的状态都能保存呢?

答案当然是否定的!

  1. 序列化对static和transient变量,是不会自动进行状态保存的。
    transient的作用就是,用transient声明的变量,不会被自动序列化。
  2. 对于Socket, Thread类,不支持序列化。若实现序列化的接口中,有Thread成员;在对该类进行序列化操作时,编译会出错!
    这主要是基于资源分配方面的原因。如果Socket,Thread类可以被序列化,但是被反序列化之后也无法对他们进行重新的资源分配;再者,也是没有必要这样实现。

下面,我们还是通过示例来查看“序列化对static和transient的处理”。

演示程序3

我们对前面的SerialTest1.java进行简单修改,得到源文件(SerialTest3.java)如下:

public class SerialTest3 {

    private static final String TMP_FILE = ".serialtest3.txt";

    public static void main(String[] args) {

        // 将“对象”通过序列化保存
        testWrite();
        // 将序列化的“对象”读出来
        testRead();
    }

    /**将 Box 对象通过序列化,保存到文件*/
    private static void testWrite(){

        try {
            // 获取文件TMP_FILE对应的对象输出流。
            // ObjectOutputStream中,只能写入“基本数据”或“支持序列化的对象”
            ObjectOutputStream out = new ObjectOutputStream(
                    new FileOutputStream(TMP_FILE));
            //创建 Box 对象,Box实现了 Serializable 序列化接口
            Box03 box = new Box03("desk", 80, 48);
            //将 box 对象写入对象输出流 out中,既相当将对象保存到文件 IMP_FILE中
            out.writeObject(box);
            //打印“Box对象”
            System.out.println("testWrite box :"+box);

            out.flush();
            out.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**从文件中读取出“序列化的Box的对象”*/
    private static void testRead(){

        try {
            //获取文件IMP_FILE 对应的对象输出流
            ObjectInputStream in = new ObjectInputStream(
                    new FileInputStream(TMP_FILE));
            //从对象输入流中,读取先前保存的对象
            Box03 box  =(Box03) in.readObject();
            //打印“Box对象”
            System.out.println("testRead box : "+box);


            in.close();

        } catch (Exception e) {
            e.printStackTrace();
        } 

    }
}

/**
 * Box类“支持序列化”。因为Box实现了Serializable接口。
 * 实际上,一个类只需要实现Serializable即可实现序列化,而不需要实现任何函数。
 * @author hu
 */
class Box03 implements Serializable{

    private String name ;
    private static int width;
    private transient int height;

    public Box03(String name,int width, int height) {
        this.name = name;
        this.width = width ;
        this.height = height;
    }

    @Override
    public String toString() {
        return "Box03 [name=" + name + ", width=" + width +", height=" + height + "]";
    }
}

SerialTest3.java 相比于 SerialTest1.java。仅仅对Box类中的 width 和 height 变量的定义进行了修改。
SerialTest1.java 中width和height定义如下 :

private int width;   
private int height; 

SerialTest3.java 中width和height定义

private static int width; 
private transient int height; 

在看后面的结果之前,我们建议大家对程序进行分析,先自己得出一个结论。
运行结果:

testWrite box :Box03 [name=desk, width=80, height=48]
testRead box : Box03 [name=desk, width=80, height=0]

结果分析:
我们前面说过,“序列化不对static和transient变量进行状态保存”。因此,testWrite()中保存Box对象时,不会保存width和height的值。这点是毋庸置疑的!但是,为什么testRead()中读取出来的Box对象的width=80,而height=0呢?

先说,为什么height=0。因为Box对象中height是int类型,而int类型的默认值是0。

再说,为什么width=80。这是因为width是static类型,而static类型就意味着所有的Box对象都共用一个height值;而在testWrite()中,我们已经将width初始化为80了。因此,我们通过序列化读取出来的Box对象的width值,也被就是80。

理解上面的内容之后,我们应该可以推断出下面的代码的运行结果。
源码如下(SerialTest4.java):

public class SerialTest4 {

    private static final String TMP_FILE = ".serialtest4.txt";

    public static void main(String[] args) {

        // 将“对象”通过序列化保存
        testWrite();
        // 将序列化的“对象”读出来
        testRead();
    }

    /**将Box对象通过序列化,保存到文件中*/
    private static void testWrite(){
        try {
            // 获取文件TMP_FILE对应的对象输出流。
            // ObjectOutputStream中,只能写入“基本数据”或“支持序列化的对象”
            ObjectOutputStream out = new ObjectOutputStream(
                    new FileOutputStream(TMP_FILE));
            //创建 Box 对象,Box实现了 Serializable 序列化接口
            Box04 box = new Box04("desk", 80, 48);
            //将 box 对象写入对象输出流 out中,既相当将对象保存到文件 IMP_FILE中
            out.writeObject(box);
            //打印“Box对象”
            System.out.println("testWrite box :"+box);
            //修改 box的值
            box = new Box04("room", 100, 50);

            out.flush();
            out.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }


    /**从文件中读取出“序列化的Box的对象”*/
    private static void testRead(){

        try {
            //获取文件IMP_FILE 对应的对象输出流
            ObjectInputStream in = new ObjectInputStream(
                    new FileInputStream(TMP_FILE));
            //从对象输入流中,读取先前保存的对象
            Box04 box  =(Box04) in.readObject();
            //打印“Box对象”
            System.out.println("testRead box : "+box);


            in.close();

        } catch (Exception e) {
            e.printStackTrace();
        } 

    }
}

/**
 * Box类“支持序列化”。因为Box实现了Serializable接口。
 * 实际上,一个类只需要实现Serializable即可实现序列化,而不需要实现任何函数。
 * @author hu
 */
class Box04 implements Serializable{

    private String name ;
    private static int width;
    private transient int height;

    public Box04(String name,int width, int height) {
        this.name = name;
        this.width = width ;
        this.height = height;
    }

    @Override
    public String toString() {
        return "Box04 [name=" + name + ", width=" + width +", height=" + height + "]";
    }
}

SerialTest4.java 相比于 SerialTest3.java,在testWrite()中添加了一行代码box = new Box(“room”, 100, 50);

运行结果:

testWrite box :Box04 [name=desk, width=80, height=48]
testRead box : Box04 [name=desk, width=100, height=0]

现在,我们更加确认“序列化不对static和transient变量进行状态保存”。但是,若我们想要保存static或transient变量,能不能办到呢?

当然可以!我们在类中重写两个方法writeObject()和readObject()即可。下面程序演示了如何手动保存static和transient变量。

演示程序4

我们对前面的SerialTest4.java进行简单修改,以达到:序列化存储static和transient变量的目的。
源码如下(SerialTest5.java):

public class SerialTest5 {

    private static final String TMP_FILE = ".serialtest5.txt";

    public static void main(String[] args) {

        // 将“对象”通过序列化保存
        testWrite();
        // 将序列化的“对象”读出来
        testRead();
    }

    /**将 Box 对象通过序列化,保存到文件*/
    private static void testWrite(){

        try {
            // 获取文件TMP_FILE对应的对象输出流。
            // ObjectOutputStream中,只能写入“基本数据”或“支持序列化的对象”
            ObjectOutputStream out = new ObjectOutputStream(
                    new FileOutputStream(TMP_FILE));
            //创建 Box 对象,Box实现了 Serializable 序列化接口
            Box05 box = new Box05("desk", 80, 48);
            //将 box 对象写入对象输出流 out中,既相当将对象保存到文件 IMP_FILE中
            out.writeObject(box);
            //打印“Box对象”
            System.out.println("testWrite box :"+box);
            //修改 box的值
            box = new Box05("room", 100, 50);

            out.flush();
            out.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**从文件中度取出“序列化的 Box对象*/
    private static void testRead(){

        try {
            //获取文件IMP_FILE 对应的对象输出流
            ObjectInputStream in = new ObjectInputStream(
                    new FileInputStream(TMP_FILE));
            //从对象输入流中,读取先前保存的对象
            Box05 box  =(Box05) in.readObject();
            //打印“Box对象”
            System.out.println("testRead box : "+box);


            in.close();

        } catch (Exception e) {
            e.printStackTrace();
        } 
    }
}

class Box05 implements Serializable{

    private String name;
    private static int width ;
    private transient int height ;

    public Box05(String name,int width, int height) {
        this.name = name;
        this.width = width ;
        this.height = height;
    }

    @Override
    public String toString() {
        return "Box05 [name=" + name + ", width=" + width +", height=" + height + "]";
    }

    private void writeObject(ObjectOutputStream out) 
            throws IOException{ 
        //使定制的 writeObject()方法可以利用自动化序列中的逻辑
        out.defaultWriteObject();
        out.writeInt(width);
        out.writeInt(height);

        System.out.println("Box--writeObject width="+width+", height="+height);
    }

    private void readObject(ObjectInputStream in) 
            throws IOException,ClassNotFoundException{ 

        //defaultReadObject()补充自动化序列化
        in.defaultReadObject();
        width = in.readInt();
        height = in.readInt();

        System.out.println("Box---readObject width="+width+", height="+height);
    }
}

运行结果:

Box–writeObject width=80, height=48
testWrite box :Box05 [name=desk, width=80, height=48]
Box—readObject width=80, height=48
testRead box : Box05 [name=desk, width=80, height=48]

程序说明:

“序列化不会自动保存static和transient变量”,因此我们若要保存它们,则需要通过writeObject()和readObject()去手动读写。

(01) 通过writeObject()方法,写入要保存的变量。writeObject的原始定义是在ObjectOutputStream.java中,我们按照如下示例覆盖即可:

private void writeObject(ObjectOutputStream out) throws IOException{ 
    out.defaultWriteObject();// 使定制的writeObject()方法可以利用自动序列化中内置的逻辑。 
    out.writeInt(ival);      // 若要保存“int类型的值”,则使用writeInt()
    out.writeObject(obj);    // 若要保存“Object对象”,则使用writeObject()
}

(02) 通过readObject()方法,读取之前保存的变量。readObject的原始定义是在ObjectInputStream.java中,我们按照如下示例覆盖即可:

private void readObject(ObjectInputStream in) throws IOException,ClassNotFoundException{ 
    in.defaultReadObject();       // 使定制的readObject()方法可以利用自动序列化中内置的逻辑。 
    int ival = in.readInt();      // 若要读取“int类型的值”,则使用readInt()
    Object obj = in.readObject(); // 若要读取“Object对象”,则使用readObject()
}

至此,我们就介绍完了“序列化对static和transient变量的处理”。
接下来,我们来研究“对于Socket, Thread类,不支持序列化”。还是通过示例来查看。

演示程序5

我们修改SerialTest5.java的源码,在Box类中添加一个Thread成员。

源码如下(SerialTest6.java):

public class SerialTest6 {

    private static final String TMP_FILE = ".serialtest6.txt";

    public static void main(String[] args) {

        // 将“对象”通过序列化保存
        testWrite();
        // 将序列化的“对象”读出来
        testRead();
    }

    /**将 Box 对象通过序列化,保存到文件*/
    private static void testWrite(){

        try {
            // 获取文件TMP_FILE对应的对象输出流。
            // ObjectOutputStream中,只能写入“基本数据”或“支持序列化的对象”
            ObjectOutputStream out = new ObjectOutputStream(
                    new FileOutputStream(TMP_FILE));
            //创建 Box 对象,Box实现了 Serializable 序列化接口
            Box06 box = new Box06("desk", 80, 48);
            //将 box 对象写入对象输出流 out中,既相当将对象保存到文件 IMP_FILE中
            out.writeObject(box);
            //打印“Box对象”
            System.out.println("testWrite box :"+box);
            //修改 box的值
            box = new Box06("room", 100, 50);

            out.flush();
            out.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**从文件中度取出“序列化的 Box对象*/
    private static void testRead(){

        try {
            //获取文件IMP_FILE 对应的对象输出流
            ObjectInputStream in = new ObjectInputStream(
                    new FileInputStream(TMP_FILE));
            //从对象输入流中,读取先前保存的对象
            Box06 box  =(Box06) in.readObject();
            //打印“Box对象”
            System.out.println("testRead box : "+box);


            in.close();

        } catch (Exception e) {
            e.printStackTrace();
        } 
    }
}
class Box06 implements Serializable{

    private String name;
    private static int width ;
    private transient int height ;
    private Thread thread = new Thread(){
        public void run() {
            System.out.println("Serializable thread");
        };
    };

    public Box06(String name,int width, int height) {
        this.name = name;
        this.width = width ;
        this.height = height;
    }

    @Override
    public String toString() {
        return "Box06 [name=" + name + ", width=" + width +", height=" + height + "]";
    }

    private void writeObject(ObjectOutputStream out) 
            throws IOException{ 
        //使定制的 writeObject()方法可以利用自动化序列中的逻辑
        out.defaultWriteObject();
        out.writeInt(width);
        out.writeInt(height);

        System.out.println("Box--writeObject width="+width+", height="+height);
    }

    private void readObject(ObjectInputStream in) 
            throws IOException,ClassNotFoundException{ 

        //defaultReadObject()补充自动化序列化
        in.defaultReadObject();
        width = in.readInt();
        height = in.readInt();

        System.out.println("Box---readObject width="+width+", height="+height);
    }
}

结果是,编译出错!

事实证明,不能对Thread进行序列化。若希望程序能编译通过,我们对Thread变量添加static或transient修饰即可!如下,是对Thread添加transient修饰的源码
(SerialTest7.java):

public class SerialTest7 {

    private static final String TMP_FILE = ".serialtest7.txt";

    public static void main(String[] args) {

        // 将“对象”通过序列化保存
        testWrite();
        // 将序列化的“对象”读出来
        testRead();
    }

    /** 将 Box 对象通过序列化,保存到文件 */
    private static void testWrite() {

        try {
            // 获取文件TMP_FILE对应的对象输出流。
            // ObjectOutputStream中,只能写入“基本数据”或“支持序列化的对象”
            ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream(TMP_FILE));
            // 创建 Box 对象,Box实现了 Serializable 序列化接口
            Box07 box = new Box07("desk", 80, 48);
            // 将 box 对象写入对象输出流 out中,既相当将对象保存到文件 IMP_FILE中
            out.writeObject(box);
            // 打印“Box对象”
            System.out.println("testWrite box :" + box);
            // 修改 box的值
            box = new Box07("room", 100, 50);

            out.flush();
            out.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /** 从文件中度取出“序列化的 Box对象 */
    private static void testRead() {

        try {
            // 获取文件IMP_FILE 对应的对象输出流
            ObjectInputStream in = new ObjectInputStream(new FileInputStream(TMP_FILE));
            // 从对象输入流中,读取先前保存的对象
            Box07 box = (Box07) in.readObject();
            // 打印“Box对象”
            System.out.println("testRead box : " + box);

            in.close();

        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

class Box07 implements Serializable {

    private String name;
    private static int width;
    private transient int height;
    private transient Thread thread = new Thread() {
        public void run() {
            System.out.println("Serializable thread");
        };
    };

    public Box07(String name, int width, int height) {
        this.name = name;
        this.width = width;
        this.height = height;
    }

    @Override
    public String toString() {
        return "Box07 [name=" + name + ", width=" + width + ", height=" + height + "]";
    }

    private void writeObject(ObjectOutputStream out) throws IOException {
        // 使定制的 writeObject()方法可以利用自动化序列中的逻辑
        out.defaultWriteObject();
        out.writeInt(width);
        out.writeInt(height);

        System.out.println("Box--writeObject width=" + width + ", height=" + height);
    }

    private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {

        // defaultReadObject()补充自动化序列化
        in.defaultReadObject();
        width = in.readInt();
        height = in.readInt();

        System.out.println("Box---readObject width=" + width + ", height=" + height);
    }
}

至此,关于“Serializable接口”来实现序列化的内容,都说完了。为什么这么说?因为,实现序列化,除了Serializable之外,还有其它的方式,就是通过实现Externalizable来实现序列化。整理下心情,下面继续对Externalizable进行了解。

3. Externalizable和完全定制序列化过程


如果一个类要完全负责自己的序列化,则实现Externalizable接口,而不是Serializable接口。
Externalizable接口定义包括两个方法writeExternal()与readExternal()。需要注意的是:声明类实现Externalizable接口会有重大的安全风险。writeExternal()与readExternal()方法声明为public,恶意类可以用这些方法读取和写入对象数据。如果对象包含敏感信息,则要格外小心。

下面,我们修改之前的SerialTest1.java测试程序;将其中的Box由“实现Serializable接口” 改为 “实现Externalizable接口”。
修改后的源码如下( ExternalizableTest1.java):

public class ExternalizableTest1 {

    private static final String TMP_FILE = ".externalizabletest1.txt";

    public static void main(String[] args) {
        // 将“对象”通过序列化保存
        testWrite();
        // 将序列化的“对象”读出来
        testRead();
    }


    /**将 Box对象通过序列化,保存到文件中*/
    private static void testWrite(){

         // 获取文件TMP_FILE对应的对象输出流。
         // ObjectOutputStream中,只能写入“基本数据”或“支持序列化的对象”
        try {
            ObjectOutputStream out = new  ObjectOutputStream(
                    new FileOutputStream(TMP_FILE));
            //创建 Box 对象,Box实现了 Serializable 序列化接口
            BoxE box = new BoxE("desk", 80, 48);
            //将 box 对象写入对象输出流 out中,既相当将对象保存到文件 IMP_FILE中
            out.writeObject(box);
            //打印“Box对象”
            System.out.println("testWrite box :"+box);

            out.flush();
            out.close();

        } catch (Exception e) {
            e.printStackTrace();
        } 
    }

    /**从文件中读取出“序列化的Box的对象”*/
    private static void testRead(){

        try {
            //获取文件IMP_FILE 对应的对象输出流
            ObjectInputStream in = new ObjectInputStream(
                    new FileInputStream(TMP_FILE));
            //从对象输入流中,读取先前保存的对象
            BoxE box  =(BoxE) in.readObject();
            //打印“Box对象”
            System.out.println("testRead box : "+box);


            in.close();

        } catch (Exception e) {
            e.printStackTrace();
        } 

    }
}

class BoxE implements Externalizable{

    private String name ;
    private int widht;
    private int height ;

    public BoxE(){}

    public BoxE(String name, int widht, int height) {
        this.name = name;
        this.widht = widht;
        this.height = height;
    }

    @Override
    public void writeExternal(ObjectOutput out) 
            throws IOException {}


    @Override
    public void readExternal(ObjectInput in)
            throws IOException, ClassNotFoundException {}

    @Override
    public String toString() {
        return "BoxE [name=" + name + ", widht=" + widht + ", height=" + height + "]";
    }

}

运行结果:

testWrite box :BoxE [name=desk, widht=80, height=48]
testRead box : BoxE [name=null, widht=0, height=0]

说明:

  1. 实现Externalizable接口的类,不会像实现Serializable接口那样,会自动将数据保存。
  2. 实现Externalizable接口的类,必须实现writeExternal()和readExternal()接口!
    否则,程序无法正常编译!
  3. 实现Externalizable接口的类,必须定义不带参数的构造函数!
    否则,程序无法正常编译!
  4. writeExternal() 和 readExternal() 的方法都是public的,不是非常安全!

接着,我们修改上面的ExternalizableTest1.java测试程序;实现Box类中的writeExternal()和readExternal()接口!
修改后的源码如下( ExternalizableTest2.java):

public class ExternalizableTest2 {

    private static final String TMP_FILE = ".externalizabletest1.txt";

    public static void main(String[] args) {
        // 将“对象”通过序列化保存
        testWrite();
        // 将序列化的“对象”读出来
        testRead();
    }


    /**将 Box对象通过序列化,保存到文件中*/
    private static void testWrite(){

        // 获取文件TMP_FILE对应的对象输出流。
        // ObjectOutputStream中,只能写入“基本数据”或“支持序列化的对象”
        try {
            ObjectOutputStream out = new  ObjectOutputStream(
                    new FileOutputStream(TMP_FILE));
            //创建 Box 对象,Box实现了 Serializable 序列化接口
            BoxE02 box = new BoxE02("desk", 80, 48);
            //将 box 对象写入对象输出流 out中,既相当将对象保存到文件 IMP_FILE中
            out.writeObject(box);
            //打印“Box对象”
            System.out.println("testWrite box :"+box);

            out.flush();
            out.close();

        } catch (Exception e) {
            e.printStackTrace();
        } 
    }

    /**从文件中读取出“序列化的Box的对象”*/
    private static void testRead(){

        try {
            //获取文件IMP_FILE 对应的对象输出流
            ObjectInputStream in = new ObjectInputStream(
                    new FileInputStream(TMP_FILE));
            //从对象输入流中,读取先前保存的对象
            BoxE02 box  =(BoxE02) in.readObject();
            //打印“Box对象”
            System.out.println("testRead box : "+box);


            in.close();

        } catch (Exception e) {
            e.printStackTrace();
        } 

    }
}

class BoxE02 implements Externalizable{

    private String name ;
    private int widht;
    private int height ;

    public BoxE02(){}

    public BoxE02(String name, int widht, int height) {
        this.name = name;
        this.widht = widht;
        this.height = height;
    }

    @Override
    public void writeExternal(ObjectOutput out) 
            throws IOException {
        out.writeObject(name);
        out.writeInt(widht);
        out.writeInt(height);
    }


    @Override
    public void readExternal(ObjectInput in)
            throws IOException, ClassNotFoundException {
        name = (String) in.readObject();
        widht = in.readInt();
        height = in.readInt();
    }

    @Override
    public String toString() {
        return "BoxE [name=" + name + ", widht=" + widht + ", height=" + height + "]";
    }

}

运行结果:

testWrite box :BoxE [name=desk, widht=80, height=48]
testRead box : BoxE [name=desk, widht=80, height=48]

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值