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)从上至下递归输出实例的数据
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方法中执行序列化和反序列化的操作:
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(); } } }
执行结果:
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(); } } }
添加序列化实现细节后,执行输出:可以输出反序列化的内容
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(); } } }
执行输出:
添加一个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(); } } }
执行后输出:
无论是实现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、如果一个对象的成员变量是一个对象,那么这个对象的数据成员也会被保存!这是能用序列化解决深拷贝的重要原因;