Java提供一种机制叫做序列化,通过有序的格式或者字节序列持久化java对象,其中包含对象的数据,还有对象的类型,和保存在对象中的数据类型。
所以,如果我们已经序列化了一个对象,那么它可以被读取并通过对象的类型和其他信息进行反序列化,并最终获取对象的原型。
ObjectInputStream 和 ObjectOutputStream对象是高级别的流对象,包含序列化和反序列化的方法。
ObjectOutputStream 拥有很多序列化对象的方法,最常用的是:
private void writeObject(ObjectOutputStream os) throws IOException { }
类似的 ObjectInputStream 提供如下方法:
private void readObject(ObjectInputStream is) throws IOException, ClassNotFoundException { }
那么哪里会需要序列化呢?序列化通常在需要通过网络传输数据,或者保存对象到文件的场合使用。这里说的数据是对象而不是文本。简单总结起来,进行对象序列化的话的主要原因就是实现对象持久化和进行网络传输.
现在的问题是,我们的网络架构和硬盘都只能识别二进制和字节,而不能识别Java对象。
序列化就是把Java对象中的value/states翻译为字节,以便通过网络传输或者保存。另外,反序列化就是通过读取字节码,并把它翻译回java对象。
serialVersionUID概念
serialVersionUID 是用于保证同一个对象(在序列化中会被用到)可以在Deserialization过程中被载入。serialVersionUID 是用于对象的版本控制。
对于序列化:
步骤如下:
让我们看一个列子:
在 src->org.arpit.javapostsforlearning创建Employee.java
1.Employee.java
package org.arpit.javapostsforlearning; import java.io.Serializable; public class Employee implements Serializable{ int employeeId; String employeeName; String department; public int getEmployeeId() { return employeeId; } public void setEmployeeId(int employeeId) { this.employeeId = employeeId; } public String getEmployeeName() { return employeeName; } public void setEmployeeName(String employeeName) { this.employeeName = employeeName; } public String getDepartment() { return department; } public void setDepartment(String department) { this.department = department; } }
就如你所见的,如果你需要序列化任何类,那么你必须实现Serializable接口,这个接口是标记接口(marker interface)。Java中的标记接口(marker interface)就是一个没有任何字段或者方法的接口,简单的来说,java中把空接口叫做标记接口(marker interface)
2.SerializeMain.java
package org.arpit.javapostsforlearning; import java.io.FileOutputStream; import java.io.IOException; import java.io.ObjectOutputStream; public class SerializeMain { /** * @author Arpit Mandliya */ public static void main(String[] args) { //创建序列化对象 Employee emp = new Employee(); emp.setEmployeeId(101); emp.setEmployeeName("Arpit"); emp.setDepartment("CS"); try { //创建文件字节输出流 FileOutputStream fileOut = new FileOutputStream("employee.ser"); //通过文件字节输出流构造对象输出流 ObjectOutputStream outStream = new ObjectOutputStream(fileOut); //写出对象 outStream.writeObject(emp); outStream.close(); fileOut.close(); }catch(IOException i) { i.printStackTrace(); } } }
这其中主要涉及到Java的I/O方面的内容,主要用到两个类FileOutputStream和ObjectOutputStream.
对于反序列化:
步骤是
在包src->org.arpit.javapostsforlearning中,创建DeserializeMain.java
3.DeserializeMain.java
package org.arpit.javapostsforlearning; import java.io.IOException; import java.io.ObjectInputStream; public class DeserializeMain { /** * @author Arpit Mandliya */ public static void main(String[] args) { Employee emp = null; try { FileInputStream fileIn =new FileInputStream("employee.ser"); ObjectInputStream in = new ObjectInputStream(fileIn); emp = (Employee) in.readObject(); in.close(); fileIn.close(); }catch(IOException i) { i.printStackTrace(); return; }catch(ClassNotFoundException c) { System.out.println("Employee class not found"); c.printStackTrace(); return; } System.out.println("Deserialized Employee..."); System.out.println("Emp id: " + emp.getEmployeeId()); System.out.println("Name: " + emp.getEmployeeName()); System.out.println("Department: " + emp.getDepartment()); } }
4.运行:
首先运行SerializeMain.java,然后运行DeserializeMain.java,你会得到如下的结果:
Deserialized Employee... Emp id: 101 Name: Arpit Department: CS
就这样,我们序列化了一个employee对象,并对它进行反序列化。这看起来和简单,但是如果其中包含对象引用,继承,那么情况就会变得复杂。接下来让我们一个接一个的看一下例子,看看如何在各种场合中实现序列化。
案例1 - 如果对象引用了其他对象,那该如何
我们已经看过最简单的序列化例子,现在看看,如何处理对象中引用了其他对象的场合。我们该如何序列化?引用对象也会被序列化吗?对的,你不需要显式的序列化引用对象。当你序列化任何对象,如果它包含引用对象,那么Java序列化会自动序列化该对象的整个对象图。例如,Employee现在引用了一个address对象,并且Address也引用了其他对象(例如,Home),那么当你序列化Employee对象的时候,所有其他引用对象,例如address和home将会被自动地被序列化。让我们来创建Address类,并它Address的对象作为引用,添加到employee类中。
Employee.java:
package org.arpit.javapostsforlearning; import java.io.Serializable; public class Employee implements Serializable{ int employeeId; String employeeName; String department; Address address; public int getEmployeeId() { return employeeId; } public void setEmployeeId(int employeeId) { this.employeeId = employeeId; } public String getEmployeeName() { return employeeName; } public void setEmployeeName(String employeeName) { this.employeeName = employeeName; } public String getDepartment() { return department; } public void setDepartment(String department) { this.department = department; } public Address getAddress() { return address; } public void setAddress(Address address) { this.address = address; } }
在 org.arpit.javapostsforlearning 包中,创建Address.java
Address.java:
package org.arpit.javapostsforlearning; public class Address { int homeNo; String street; String city; public Address(int homeNo, String street, String city) { super(); this.homeNo = homeNo; this.street = street; this.city = city; } public int getHomeNo() { return homeNo; } public void setHomeNo(int homeNo) { this.homeNo = homeNo; } public String getStreet() { return street; } public void setStreet(String street) { this.street = street; } public String getCity() { return city; } public void setCity(String city) { this.city = city; } }
在包org.arpit.javapostsforlearning中,创建SerializeDeserializeMain.java
SerializeDeserializeMain.java:
package org.arpit.javapostsforlearning; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; public class SerializeDeserializeMain { /** * @author Arpit Mandliya */ public static void main(String[] args) { Employee emp = new Employee(); emp.setEmployeeId(101); emp.setEmployeeName("Arpit"); emp.setDepartment("CS"); Address address=new Address(88,"MG road","Pune"); emp.setAddress(address); //Serialize try { FileOutputStream fileOut = new FileOutputStream("employee.ser"); ObjectOutputStream outStream = new ObjectOutputStream(fileOut); outStream.writeObject(emp); outStream.close(); fileOut.close(); }catch(IOException i) { i.printStackTrace(); } //Deserialize emp = null; try { FileInputStream fileIn =new FileInputStream("employee.ser"); ObjectInputStream in = new ObjectInputStream(fileIn); emp = (Employee) in.readObject(); in.close(); fileIn.close(); }catch(IOException i) { i.printStackTrace(); return; }catch(ClassNotFoundException c) { System.out.println("Employee class not found"); c.printStackTrace(); return; } System.out.println("Deserialized Employee..."); System.out.println("Emp id: " + emp.getEmployeeId()); System.out.println("Name: " + emp.getEmployeeName()); System.out.println("Department: " + emp.getDepartment()); address=emp.getAddress(); System.out.println("City :"+address.getCity()); } }
运行它:
当你运行SerializeDeserializeMain.java。你会得到这样的结果:
java.io.NotSerializableException: org.arpit.javapostsforlearning.Address at java.io.ObjectOutputStream.writeObject0(Unknown Source) at java.io.ObjectOutputStream.defaultWriteFields(Unknown Source) at java.io.ObjectOutputStream.writeSerialData(Unknown Source) at java.io.ObjectOutputStream.writeOrdinaryObject(Unknown Source) at java.io.ObjectOutputStream.writeObject0(Unknown Source) at java.io.ObjectOutputStream.writeObject(Unknown Source)
我们将解释哪里出错了。我忘记了说,Address 类也必须是serializable。那么Address类必须继承serialzable接口。
Address.java:
import java.io.Serializable; public class Address implements Serializable{ int homeNo; String street; String city; public Address(int homeNo, String street, String city) { super(); this.homeNo = homeNo; this.street = street; this.city = city; } public int getHomeNo() { return homeNo; } public void setHomeNo(int homeNo) { this.homeNo = homeNo; } public String getStreet() { return street; } public void setStreet(String street) { this.street = street; } public String getCity() { return city; } public void setCity(String city) { this.city = city; } }
再次运行:
当你再次运行SerializeDeserializeMain.java。你可以得到如下的结果
Deserialized Employee... Emp id: 101 Name: Arpit Department: CS City :Pune
案例2:如果我们不能访问引用对象的源代码(例如,你不能访问上面的Address类的源码)
如果我们不能访问到address类,那么我们该如何在Address类中实现serializable接口?是否有另外的途径来实现呢?对的,你可以创建另外一个类,并继承Address,然后让它继承serializable接口,但是对于下面的情况,这个方案会失败:
·如果引用类被定义为final
·如果引用类引用了另外一个非可序列化的对象
那么,我们该如何序列化Employee对象?解决的办法是,标记transient。如果你不需要序列化任何字段,只需把它标记为transient。
transient Address address
在Employee类中,标记了address为transient之后,运行程序。你会得到nullPointerException,因为在反序列化过程中,Address引用将会是null。