Java 实现序列化接口 Serializable

java对象实现Serializable接口

Serializable接口在JDK1.1开始声明  所在包为java.io中,仅声明为一个接口,并没有任何方法。

1.序列化和反序列化

(1)Java序列化是指把Java对象转换为字节序列的过程,而Java反序列化是指把字节序列恢复为Java对象的过程;

(2)序列化:对象序列化的最主要的用处就是在传递和保存对象的时候,保证对象的完整性和可传递性。序列化是把对象转换成有序字节流,以便在网络上传输或者保存在本地文件中。序列化后的字节流保存了Java对象的状态以及相关的描述信息。序列化机制的核心作用就是对象状态的保存与重建。

(3)反序列化:客户端从文件中或网络上获得序列化后的对象字节流后,根据字节流中所保存的对象状态及描述信息,通过反序列化重建对象。

  本质上讲,序列化就是把实体对象状态按照一定的格式写入到有序字节流,反序列化就是从有序字节流重建对象,恢复对象状态。

2、序列化与反序列化作用

       当两个进程进行远程通信时,可以相互发送各种类型的数据,包括文本、图片、音频、视频等, 而这些数据都会以二进制序列的形式在网络上传送。

那么当两个Java进程进行通信时,在进程间进行对象传输,就是通过Java序列化与反序列化实现。

换句话说,一方面,发送方需要把这个Java对象转换为字节序列(序列化),然后在网络上传送;另一方面,接收方需要从字节序列中恢复出Java对象(反序列化)。

当我们明晰了为什么需要Java序列化和反序列化后,我们很自然地会想Java序列化的好处。其好处:一是实现了数据的持久化,通过序列化可以把数据永久地保存到硬盘上(通常存放在文件里);二是,利用序列化实现远程通信,即在网络上传送对象的字节序列。

总的来说可以归结为以下几点:

(1)永久性保存对象,保存对象的字节序列到本地文件或者数据库中;
(2)通过序列化以字节流的形式使对象在网络中进行传递和接收;
(3)通过序列化在进程间传递对象;

3、序列化算法一般会按步骤做如下事情:

(1)将对象实例相关的类元数据输出。
(2)递归地输出类的超类描述直到不再有超类。
(3)类元数据完了以后,开始从最顶层的超类开始输出对象实例的实际数据值。
(4)从上至下递归输出实例的数据

二、Java如何实现序列化和反序列化

1、JDK类库中序列化和反序列化API

(1)java.io.ObjectOutputStream:表示对象输出流;

        writeObject(Object obj)方法可以对参数指定的obj对象进行序列化,把得到的字节序列写到一个目标输出流中;

(2)java.io.ObjectInputStream:表示对象输入流;

       readObject()方法源输入流中读取字节序列,再把它们反序列化成为一个对象,并将其返回;

2、实现序列化的要求

只有实现了Serializable或Externalizable接口的类的对象才能被序列化,否则抛出异常!

3、实现Java对象序列化与反序列化的方法

假定一个类,它的对象需要序列化,可以有如下三种方法:

(1)直接实现Serializable接口(默认序列化机制),则可以按照以下方式进行序列化和反序列化

        ObjectOutputStream采用默认的序列化方式,对该类对象的非transient的实例变量进行序列化。
        ObjcetInputStream采用默认的反序列化方式,对该类对象的非transient的实例变量进行反序列化。

       如果仅仅只是让某个类实现Serializable接口,而没有其它任何处理的话,则就是使用默认序列化机制。使用默认机制,在序列化对象时,不仅会序列化当前对象本身,还会对该对象引用的其它对象也进行序列化,同样地,这些其它对象引用的另外对象也将被序列化,以此类推。所以,如果一个对象包含的成员变量是容器类对象,而这些容器所含有的元素也是容器类对象,那么这个序列化的过程就会较复杂,开销也较大。

(2)实现Serializable接口,并且类中定义了readObject(ObjectInputStream in)和writeObject(ObjectOutputSteam out),则采用以下方式进行序列化与反序列化。

        ObjectOutputStream调用对象的writeObject(ObjectOutputStream out)的方法进行序列化。
        ObjectInputStream会调用对象的readObject(ObjectInputStream in)的方法进行反序列化。

(3)若类实现了Externalnalizable接口,且类必须实现readExternal(ObjectInput in)和writeExternal(ObjectOutput out)方法,则按照以下方式进行序列化与反序列化。

       ObjectOutputStream调用对象的writeExternal(ObjectOutput out))的方法进行序列化。
       ObjectInputStream会调用对象的readExternal(ObjectInput in)的方法进行反序列化。

4、JDK类库中序列化/反序列化步骤

实例:

public class Car implements Serializable {
    private static final long serialVersionUID = -7564835224189971712L;

    private String brand;

    private double price;

    private String color;

    private static String status = "售卖中";

    public Car(String brand, double price, String color) {
        this.brand = brand;
        this.price = price;
        this.color = color;
    }

    @Override
    public String toString() {
        final StringBuffer sb = new StringBuffer("Car{");
        sb.append("brand='").append(brand).append('\'');
        sb.append(", price=").append(price);
        sb.append(", color='").append(color).append('\'');
        sb.append('}');
        return sb.toString();
    }

    //忽略getter和setter方法

    //序列化-反序列化
    public static void main(String[] args) {
        try {
            //序列化过程
            //1.创建一个对象输出流,它可以包装一个其它类型的目标输出流,如文件输出流:
            FileOutputStream fileOutputStream = new FileOutputStream("car.info");
            ObjectOutputStream objectOutputStream = new ObjectOutputStream(fileOutputStream);
            //2.通过对象输出流的writeObject()方法写对象:
            objectOutputStream.writeObject(new Car("红旗",600000.00,"亮黑"));
            objectOutputStream.flush();
            objectOutputStream.close();


            //反序列化过程
            //1.创建一个对象输入流,它可以包装一个其它类型输入流,如文件输入流:
            FileInputStream fileInputStream = new FileInputStream("car.info");
            ObjectInputStream objectInputStream = new ObjectInputStream(fileInputStream);
            //2.通过对象输出流的readObject()方法读取对象:
            Car car = (Car) objectInputStream.readObject();
            System.out.println(car);

        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
}

在main方法中执行序列化和反序列化的操作:

执行后输出:通过反序列化还原序列化前的数据
Java 实现序列化接口 Serializable_第1张图片

5、serialVersionUID

凡是实现Serializable接口的类都有一个表示序列化版本标识符的静态变量:private static final long serialVersionUID; 类的serialVersionUID的默认值完全依赖于Java编译器的实现,对于同一个类,用不同的Java编译器编译,有可能会导致不同的serialVersionUID。显式地定义serialVersionUID有两种用途: 

  1)在某些场合,希望类的不同版本对序列化兼容,因此需要确保类的不同版本具有相同的serialVersionUID;在某些场合,不希望类的不同版本对序列化兼容,因此需要确保类的不同版本具有不同的serialVersionUID。 
 

  2)当你序列化了一个类实例后,希望更改一个字段或添加一个字段,不设置serialVersionUID,所做的任何更改都将导致无法反序化旧有实例,并在反序列化时抛出一个异常。如果你添加了serialVersionUID,在反序列旧有实例时,新添加或更改的字段值将设为初始化值(对象为null,基本类型为相应的初始默认值),字段被删除将不设置。

6、Externalizable接口
    无论是使用transient关键字,还是使用writeObject()和readObject()方法,其实都是基于Serializable接口的序列化。JDK中提供了另一个序列化接口--Externalizable,使用该接口之后,之前基于Serializable接口的序列化机制就将失效。

6.1 实现Externalizable接口,而不写序列化的细节

public class Car2 implements Externalizable {

    private String brand;

    private double price;

    private String color;

    private static String status = "售卖中";

    public Car2() {
        System.out.println("Car2 none-arg constructor");
    }

    public Car2(String brand, double price, String color) {
        System.out.println("Car2 arg constructor");
        this.brand = brand;
        this.price = price;
        this.color = color;
    }

    @Override
    public String toString() {
        final StringBuffer sb = new StringBuffer("Car{");
        sb.append("brand='").append(brand).append('\'');
        sb.append(", price=").append(price);
        sb.append(", color='").append(color).append('\'');
        sb.append('}');
        return sb.toString();
    }

    //必须实现Externalizable的 writeExternal(ObjectOutput out)方法,不写具体内容
    public void writeExternal(ObjectOutput out) throws IOException {
    }

    //必须实现Externalizable的 readExternal(ObjectInput in)方法,不写具体内容
    public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
    }

    //序列化-反序列化
    public static void main(String[] args) {
        try {
            //序列化过程
            //1.创建一个对象输出流,它可以包装一个其它类型的目标输出流,如文件输出流:
            FileOutputStream fileOutputStream = new FileOutputStream("car.info");
            ObjectOutputStream objectOutputStream = new ObjectOutputStream(fileOutputStream);
            //2.通过对象输出流的writeObject()方法写对象:
            objectOutputStream.writeObject(new Car2("红旗",600000.00,"亮黑"));
            objectOutputStream.flush();
            objectOutputStream.close();

            //反序列化过程
            //1.创建一个对象输入流,它可以包装一个其它类型输入流,如文件输入流:
            FileInputStream fileInputStream = new FileInputStream("car.info");
            ObjectInputStream objectInputStream = new ObjectInputStream(fileInputStream);
            //2.通过对象输出流的readObject()方法读取对象:
            Car2 car = (Car2) objectInputStream.readObject();
            System.out.println(car);

        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
}

执行结果:

Java 实现序列化接口 Serializable_第2张图片

      Externalizable继承于Serializable,当使用该接口时,序列化的细节需要由程序员去完成。如上所示的代码,由于writeExternal()与readExternal()方法未作任何处理,那么该序列化行为将不会保存/读取任何一个字段。这也就是为什么输出结果中所有字段的值均为空。
    另外,若使用Externalizable进行序列化,当读取对象时,会调用被序列化类的无参构造器去创建一个新的对象,然后再将被保存对象的字段的值分别填充到新对象中。这就是为什么在此次序列化过程中Person类的无参构造器会被调用。由于这个原因,实现Externalizable接口的类必须要提供一个无参的构造器,且它的访问权限为public。

6.2 对上面的代码改造,添加序列化细节

public class Car2 implements Externalizable {

    private String brand;

    private double price;

    private String color;

    private static String status = "售卖中";

    public Car2() {
        System.out.println("Car2 none-arg constructor");
    }

    public Car2(String brand, double price, String color) {
        System.out.println("Car2 arg constructor");
        this.brand = brand;
        this.price = price;
        this.color = color;
    }

    @Override
    public String toString() {
        final StringBuffer sb = new StringBuffer("Car{");
        sb.append("brand='").append(brand).append('\'');
        sb.append(", price=").append(price);
        sb.append(", color='").append(color).append('\'');
        sb.append('}');
        return sb.toString();
    }

    //必须实现Externalizable的 writeExternal(ObjectOutput out)方法
    public void writeExternal(ObjectOutput out) throws IOException {
        out.writeObject(brand);
        out.writeDouble(price);
        out.writeObject(color);
    }

    //必须实现Externalizable的 readExternal(ObjectInput in)方法
    public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
        brand = (String) in.readObject();
        price = in.readDouble();
        color = (String) in.readObject();
    }

    //序列化-反序列化
    public static void main(String[] args) {
        try {
            //序列化过程
            //1.创建一个对象输出流,它可以包装一个其它类型的目标输出流,如文件输出流:
            FileOutputStream fileOutputStream = new FileOutputStream("car.info");
            ObjectOutputStream objectOutputStream = new ObjectOutputStream(fileOutputStream);
            //2.通过对象输出流的writeObject()方法写对象:
            objectOutputStream.writeObject(new Car2("红旗",600000.00,"亮黑"));
            objectOutputStream.flush();
            objectOutputStream.close();

            //反序列化过程
            //1.创建一个对象输入流,它可以包装一个其它类型输入流,如文件输入流:
            FileInputStream fileInputStream = new FileInputStream("car.info");
            ObjectInputStream objectInputStream = new ObjectInputStream(fileInputStream);
            //2.通过对象输出流的readObject()方法读取对象:
            Car2 car = (Car2) objectInputStream.readObject();
            System.out.println(car);

        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
}

添加序列化实现细节后,执行输出:可以输出反序列化的内容

Java 实现序列化接口 Serializable_第3张图片

6.3 、readResolve()方法

当我们使用Singleton模式时,应该是期望某个类的实例应该是唯一的,但如果该类是可序列化的,那么情况可能会略有不同。此时对第2节使用的Car类进行修改,使其实现Singleton模式,如下所示:

6.3.1、未添加readResolve()方法:

public class Car implements Serializable {
    private static final long serialVersionUID = -7564835224189971712L;

    private static class InstanceHolder {
        private static final Car instatnce = new Car("红旗",600000.00,"亮黑");
    }

    public static Car getInstance() {
        return InstanceHolder.instatnce;
    }

    private String brand;

    private double price;

    private String color;

    private static String status = "售卖中";

    public Car() {
        System.out.println("Car none-arg constructor");
    }

    public Car(String brand, double price, String color) {
        System.out.println("Car arg constructor");
        this.brand = brand;
        this.price = price;
        this.color = color;
    }

    @Override
    public String toString() {
        final StringBuffer sb = new StringBuffer("Car{");
        sb.append("brand='").append(brand).append('\'');
        sb.append(", price=").append(price);
        sb.append(", color='").append(color).append('\'');
        sb.append('}');
        return sb.toString();
    }

    //忽略getter和setter方法

    //序列化-反序列化
    public static void main(String[] args) {
        try {
            //序列化过程
            //1.创建一个对象输出流,它可以包装一个其它类型的目标输出流,如文件输出流:
            FileOutputStream fileOutputStream = new FileOutputStream("car.info");
            ObjectOutputStream objectOutputStream = new ObjectOutputStream(fileOutputStream);
            //2.通过对象输出流的writeObject()方法写对象:
            objectOutputStream.writeObject(Car.getInstance());
            objectOutputStream.flush();
            objectOutputStream.close();

            //反序列化过程
            //1.创建一个对象输入流,它可以包装一个其它类型输入流,如文件输入流:
            FileInputStream fileInputStream = new FileInputStream("car.info");
            ObjectInputStream objectInputStream = new ObjectInputStream(fileInputStream);
            //2.通过对象输出流的readObject()方法读取对象:
            Car newCar = (Car) objectInputStream.readObject();
            System.out.println(newCar);

            System.out.println("Car.getInstance() == newCar : "+ (Car.getInstance() == newCar));

        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
}

执行输出:

Java 实现序列化接口 Serializable_第4张图片

添加一个readResolve()方法,在该方法中直接返回Car的单例对象

6.3.2、 添加readResolve()方法后,实现单例

public class Car implements Serializable {
    private static final long serialVersionUID = -7564835224189971712L;

    private static class InstanceHolder {
        private static final Car instatnce = new Car("红旗",600000.00,"亮黑");
    }

    public static Car getInstance() {
        return InstanceHolder.instatnce;
    }

    private String brand;

    private double price;

    private String color;

    private static String status = "售卖中";

    public Car() {
        System.out.println("Car none-arg constructor");
    }

    public Car(String brand, double price, String color) {
        System.out.println("Car arg constructor");
        this.brand = brand;
        this.price = price;
        this.color = color;
    }

    @Override
    public String toString() {
        final StringBuffer sb = new StringBuffer("Car{");
        sb.append("brand='").append(brand).append('\'');
        sb.append(", price=").append(price);
        sb.append(", color='").append(color).append('\'');
        sb.append('}');
        return sb.toString();
    }

    //忽略getter和setter方法

    private Object readResolve() throws ObjectStreamException {
        return InstanceHolder.instatnce;
    }

    //序列化-反序列化
    public static void main(String[] args) {
        try {
            //序列化过程
            //1.创建一个对象输出流,它可以包装一个其它类型的目标输出流,如文件输出流:
            FileOutputStream fileOutputStream = new FileOutputStream("car.info");
            ObjectOutputStream objectOutputStream = new ObjectOutputStream(fileOutputStream);
            //2.通过对象输出流的writeObject()方法写对象:
            objectOutputStream.writeObject(Car.getInstance());
            objectOutputStream.flush();
            objectOutputStream.close();
            
            //反序列化过程
            //1.创建一个对象输入流,它可以包装一个其它类型输入流,如文件输入流:
            FileInputStream fileInputStream = new FileInputStream("car.info");
            ObjectInputStream objectInputStream = new ObjectInputStream(fileInputStream);
            //2.通过对象输出流的readObject()方法读取对象:
            Car newCar = (Car) objectInputStream.readObject();
            System.out.println(newCar);

            System.out.println("Car.getInstance() == newCar : "+ (Car.getInstance() == newCar));

        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
}

执行后输出:

Java 实现序列化接口 Serializable_第5张图片

       无论是实现Serializable接口,或是Externalizable接口,当从I/O流中读取对象时,readResolve()方法都会被调用到。实际上就是用readResolve()中返回的对象直接替换在反序列化过程中创建的对象,而被创建的对象则会被垃圾回收掉。

三、关注点

1、序列化时,只对对象的状态进行保存,而不管对象的方法;

2、当一个父类实现序列化,子类自动实现序列化,不需要显式实现Serializable接口;

3、当一个对象的实例变量引用其他对象,序列化该对象时也把引用对象进行序列化,如果引用的该对象不可被序列化,那么整个序列化操作将会失败,并且会抛出一个NotSerializableException。我们可以将这个引用标记为transient,那么对象仍然可以序列化

4、并非所有的对象都可以序列化,至于为什么不可以,有很多原因了,比如:

       安全方面的原因,比如一个对象拥有private,public等field,对于一个要传输的对象,比如写到文件,或者进行RMI传输等等,在序列化进行传输的过程中,这个对象的private等域是不受保护的;

       资源分配方面的原因,比如socket,thread类,如果可以序列化,进行传输或者保存,也无法对他们进行重新的资源分配,而且,也是没有必要这样实现;

5、声明为 static 和 transient 类型的成员数据不能被序列化。因为static代表类的状态,transient代表对象的临时数据。

7、Java有很多基础类已经实现了serializable接口,比如String,Vector等。但是也有一些没有实现serializable接口的;

8、如果一个对象的成员变量是一个对象,那么这个对象的数据成员也会被保存!这是能用序列化解决深拷贝的重要原因;

你可能感兴趣的:(java)