Serializable


Serializable 接口–java.io.serializable
    - 作用:类通过实现 java.io.Serializable 接口以启用其序列化功能。未实现此接口的类将无法使其任何状态序列化或反序列化。可序列化类的所有子类型本身都是可序列化的。序列化接口没有方法或字段,仅用于标识可序列化的语义。
    - 实现了此接口的类有:String, Integer, Boolean, Long, ArrayList, List, File 等等。
    - 当java内存有一个对象的时候,我们想把它持久化保存起来,以后又可以从保存的数据中恢复成对象,这种情况经常在java中出现;sun公司为了方便实现这种功能,为用户提供了java.io.Serializable这个接口,这个接口中没有方法(又称为mini接口);
    - 在java编译器编译的时候对这种mini接口的对象进行特殊的编译,如果你的对象实现了serializable接口,那么就可以通过另一个工具ObjectOutputStream的方法writeObject(obj)来存储对象,而不需要具体知道存储该对象的具体细节;
    - 当恢复对象的时候,便可以通过ObjectInputStream.readObject(obj)来恢复对象;
    - 由于对于文件的存储目标是未知的,可以是硬盘也可以是网络上的某个主机,故ObjectInputStream和ObjectOutputStream并不是直接操作硬盘,也不是直接操作网络,而是操作可以直接访问物理设备的流的对象,如FileInputStream和FileOutputStream,即ObjectInputStream和ObjectOutputStream可以将直接进行底层操作的流FileInputStream和FileOutputStream包装起来,形成包装类,或者叫包装流;
    - 在形成包装类之后,对于需要保存的对象,先由包装类的外层将该对象转换为二进制字节,再将这些字节传递给内层可以直接进行物理设备操作的流如FileOutputStream,通过底层流保存到物理设备;
    - 包装的思想:首先,有个物理设备,如硬盘或网络,在内存中会有个流,如FileOutputStream,这个流只能有个write(byte[])方法,该方法写的是字节。而现在要将一个对象写入物理设备,故需要一个类来进行对象到字节的转换,这个转换类就是ObjectInputStream和ObjectOutputStream,通过这两个类转换,将转换后的字节数据交给FileOutputStream来进行物理设备操作。简单地说,就是一个分层的思想,也是“各尽其责”的思想。对象保存到物理设备,这个过程由两层完成,上层只负责将对象转换成字节数据,下层只负责将接收到的字节写入物理设备。
    - 所以包装流(如ObjectOutputStream)并不真正连接到底层物理设备,它起到一个中转的作用,因此它的构造方法一定要传递给它一个与底层设备相关的流,前者是把数据转换完了交给后者去读写物理设备;对于物理设备是什么设备,包装流并不知道,只有传递给它的底层设备相关的流(如FileOutputStream)知道。

JAVA序列化详解(串行化)-Serializable接口

 

 

        假设你想保存一个或多个对象的状态。如果Java不具有序列化功能,就不得不使用某个I/O类写出你想保存的所有对象的实例变量的状态。最糟糕的事情是尝试重构一个新对对象,而且该对象实际上与你正尝试保存的对象完全相同。此时,你需要自己的协议,来写出和恢复每个对象的状态,或者你可以用错误的值来终止设置变量。例如,假设你存储了一个对象,它具有用于表示高度和宽度的实例变量。在你存储对象的状态时,可以把高度和宽度作为两个int写出到一个文件中,但是,你写出它们的次序是至关重要的。它实在太容易了,以至于不会重建对象,但是会混合高度和宽度值——把保存的高度作为新对象的宽度值,反之亦然。

        序列化允许简单地说“保存这个对象及其所有实例变量”。实际上,更有趣的是,由于你可以增加“……除非我显示地将一个变量标识为transient,这意味着,不会把这个瞬态变量的值作为对象的序列化状态的一部分包括进来”。

 

1、使用ObjectOutputStream和ObjectInputStream

 

        基本序列化的神奇能力只是由两个方法产生的:一个方法用于序列化对象并把它们写到一个流,第二个方法用于读取流 反序列化对象。

        ObjectOutputStream.writeObject(); //serialize and write

        ObjectInputStream.readObject(); //read and deserialize

        java.io.ObjectOutputStream和java.io.ObjectInputStream类被认为是java.io包中的高级类,这意味着你将把它们包围在低级类的周围,如java.io.FileOutputStream和java.io.FileInputStream。下面是一个小程序,它将创建一个(Cat)对象,序列化它,然后反序列化它:


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

public class SerializeCat {
    public static void main(String[] args) {
         Cat c = new Cat();
        try {
             FileOutputStream fs = new FileOutputStream("testSer.ser");
             ObjectOutputStream os = new ObjectOutputStream(fs);
             os.writeObject(c);
             os.close();
         }
        catch(Exception e) {
             e.printStackTrace();
         }
        
        try {
             FileInputStream fis = new FileInputStream("testSer.ser");
             ObjectInputStream ois = new ObjectInputStream(fis);
             c = (Cat)ois.readObject();
             ois.close();
         }
        catch(Exception   e) {
             e.printStackTrace();
         }
     }
}
class Cat implements Serializable {}

        让我们看看这个示例中的几个关键点:

        (1)我们声明Cat类实现了Serializable接口。Serializable接口是一个标识接口;它没有要实现的方法(在后面的几节中我们将介绍关于何时需要声明Serializable类的各种规则)。

        (2)建立一个Cat对象,我们知道它是可序列化的。

        (3)通过调用writeObject()方法序列化Cat对象c。在我们可以实际地序列化Cat之前,有大量的准备工作要做。首先,我们必须把我们所有与I/O相关的代码放到try/catch块中。接下来,我们必须创建一个FileOutputStream,以把对象写入其中。然后,我们在一个 ObjectOutputStream中包装FileOutputStream,ObjectOutputStream是带有我们需要的神奇序列化方法的类。记住:调用writeObject()会执行两个任务:它序列化对象,然后把经过序列化的对象写到文件。

        (4)通过调用readObject()方法反序列化Cat对象。readObject()方法返回一个Object,因此,我们必须把饭序列化的对象强制转换回Cat。同样,我们必须经过典型的I/O循环来做到这一点。

        这是实际中的序列化机制的主体部分。在后面,我们将介绍一些与序列化关联的更复杂的问题。

 

 

2、对象图

 

        保存一个对象实际上意味着什么?如果实例变量全部是基本类型,那么这个问题非常简单。但是,如果实例变量自身指向对象的引用,又会如何呢?会保存什么内容?显然,在Java中,保存引用变量的实际值没有任何意义,因为Java引用的值只有JVM单一实例的环境中才有意义。换句话说,如果你尝试在JVM的另一个实例中恢复对象,即使运行在对对象进行最初序列化的同一台计算机上,引用也毫无用处。

        但是,该引用所引用的对象会发生什么情况?观察下面这个类:


class Dog {
    private Collar theCollar;
    private int dogSize;
    public Dog(Collar collar, int size) {
         theCollar = collar;
         dogSize = size;
     }
    public Collar getCollar() {
        return theCollar;
     }
}
class Collar {
    private int collarSize;
    public Collar(int size) {
         collarSize = size;
     }
    public int getCollarSize() {
        return collarSize;
     }
}

        现在建立一个Dog……首先,为Dog建立一个Collar:

        Collar c = new Collar(3);

        然后,建立一个新的Dog,并把Collar传递给它:

        Dog d = new Dog(c, 8);

        现在,如果保存Dog,会发生什么情况?如果目标是保存然后恢复Dog,那么恢复的Dog就是保存的Dog的完全一样的复制品,然后该Dog需要一个 Collar,它是保存Dog时Dog的Collar的完全一样的复制品。这意味着Dog和Collar都应该被保存。

        如果Collar自身具有对其他对象的引用(例如,可能是一个Color对象),又会发生什么情况?这使问题极快地变得相当复杂。如果它要求程序员知道 Dog引用的每个对象的内部结构,使得程序员可以确信保存了所有这些对象的所有状态……即使对于最简单的对象,这也是一个梦魇。

        幸运的是,Java序列化机制处理了所有这些工作。当你序列化一个对象时,Java序列化机制会保存对象的完整的“对象图”。这是恢复保存的对象所需一切内容的深复制。例如,如果序列化一个Dog对象,将自动序列化Collar。如果Collar类包含一个指向另一对象的引用,则那个对象也会被序列化,以此类推。你必须关心其保存和恢复的唯一对象就是Dog。还需要其他的对象,以通过序列化机制自动地完全重构被保存(和恢复)的Dog。

        记住:你确实必须通过实现Serializable接口,有意识地选择创建可序列化的对象。例如,如果我们想保存Dog对象,我们将不得不按如下方式修改Dog类:


class Dog implements Serializable {
    //the rest of the code as before
         //Serializable has no methods to implement
}

        现在,我们可以用下面的代码保存Dog:


import java.io.FileOutputStream;
import java.io.ObjectOutputStream;
public class SerializeDog {
    public static void main(String[] args) {
         Collar c = new Collar(3);
         Dog d = new Dog(c, 8);
        try {
             FileOutputStream fs = new FileOutputStream("testSer.ser");
             ObjectOutputStream os = new ObjectOutputStream(fs);
             os.writeObject(d);
             os.close();
         }
        catch(Exception e) {
             e.printStackTrace();
         }
     }
}

        但是,当我们在运行这段代码时,将会得到一个运行时异常,如下:

        java.io.NotSerializableException:Collar

        我们忘了什么?Collar类也必须是可序列化的。如果我们修改Collar类,并使之可序列化,那么就不会再有任何问题:


class Collar implements Serializable {
    private int collarSize;
    public Collar(int size) {
         collarSize = size;
     }
    public int getCollarSize() {
        return collarSize;
     }
}

        完整的程序清单如下:


import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
public class SerializeDog {
    public static void main(String[] args) {
         Collar c = new Collar(3);
         Dog d = new Dog(c, 8);
         System.out.println("before: collar size is "
                 + d.getCollar().getCollarSize());
        try {
             FileOutputStream fs = new FileOutputStream("testSer.ser");
             ObjectOutputStream os = new ObjectOutputStream(fs);
             os.writeObject(d);
             os.close();
         }
        catch(Exception e) {
             e.printStackTrace();
         }
        try {
             FileInputStream fis = new FileInputStream("testSer.ser");
             ObjectInputStream ois = new ObjectInputStream(fis);
             d = (Dog)ois.readObject();
             ois.close();
         }
        catch(Exception e) {
             e.printStackTrace();
         }
         System.out.println("after: collar size is "
                 + d.getCollar().getCollarSize());
     }
}
class Dog implements Serializable {
    private Collar theCollar;
    private int dogSize;
    public Dog(Collar collar, int size) {
         theCollar = collar;
         dogSize = size;
     }
    public Collar getCollar() {
        return theCollar;
     }
}
class Collar implements Serializable {
    private int collarSize;
    public Collar(int size) {
         collarSize = size;
     }
    public int getCollarSize() {
        return collarSize;
     }
}

        其输出如下:

        before: collar size is 3
        after: collar size is 3

 

        但是,如果我们不能访问Collar类的源代码,则会如何?换句话说,如果使得Collar类可序列化不是一个选项,则会如何?我们会坚持一个不可序列化的Dog吗?

        显然,如果我们可以对Collar类再分子类,把孩子标识为Serializable,然后使用Collar子类而不是Collar类。但是,由于 以下几个潜在的原因,并非总能选择使用这样的方法:

        (1)Collar类可能是最终类,从而无法再分子类;

        (2)Collar类自身可能引用其他不可序列化的对象,并且如果不知道Collar的内部结构,就不能执行所有这些修正(假定你甚至尝试采用这种方法);

        (3)由于和你的设计相关的其他原因,再分子类不可选。

        那么……如果你想保存一个Dog,接下来要做什么?

        这时,transient修饰符就闪亮登场了。如果把Dog的Collar实例变量标识为transient,那么在序列化期间,序列化将会简单地跳过Collar


class Dog implements Serializable {
    private transient Collar theCollar;  //Add transient
    private int dogSize;
    public Dog(Collar collar, int size) {
         theCollar = collar;
         dogSize = size;
     }
    public Collar getCollar() {
        return theCollar;
     }
}
class Collar {
    private int collarSize;
    public Collar(int size) {
         collarSize = size;
     }
    public int getCollarSize() {
        return collarSize;
     }
}

        现在,我们具有一个可序列化的Dog,以及一个不可序列化的Collar,但是,Dog把Collar标识为transient。输出如下:

        before: collar size is 3

        Exception in thread "main" java.lang.NullPointerException

        那么,现在我们要做什么

 

posted @ 2015-07-09 00:31  Uncle_Nucky  阅读(234)  评论(0)    收藏  举报