Java提供一种机制叫做序列化,通过有序的格式或者字节序列持久化java对象,其中包含对象的数据,还有对象的类型,和保存在对象中的数据类型。
所以,如果我们已经序列化了一个对象,那么它可以被读取并通过对象的类型和其他信息进行反序列化,并最终获取对象的原型。
ObjectInputStream 和 ObjectOutputStream对象是高级别的流对象,包含序列化和反序列化的方法。
ObjectOutputStream 拥有很多序列化对象的方法,最常用的是:
1 |
private void
writeObject(ObjectOutputStream os) throws
IOException |
类似的 ObjectInputStream 提供如下方法:
1 |
private
void readObject(ObjectInputStream is)
throws IOException, ClassNotFoundException |
那么哪里会需要序列化呢?序列化通常在需要通过网络传输数据,或者保存对象到文件的场合使用。这里说的数据是对象而不是文本。
现在的问题是,我们的网络架构和硬盘都只能识别二进制和字节,而不能识别Java对象。
序列化就是把Java对象中的value/states翻译为字节,以便通过网络传输或者保存。另外,反序列化就是通过读取字节码,并把它翻译回java对象。
serialVersionUID概念
serialVersionUID 是用于保证同一个对象(在序列化中会被用到)可以在Deserialization过程中被载入。serialVersionUID 是用于对象的版本控制。你可以参考serialVersionUID
in java serialization获取更多信息。
对于序列化:
步骤如下:
让我们看一个列子:
在 src->org.arpit.javapostsforlearning 创建Employee.java
1.Employee.java
01 |
package org.arpit.javapostsforlearning; |
02 |
import java.io.Serializable; |
03 |
public class
Employee implements
Serializable{ |
09 |
public
int getEmployeeId() { |
12 |
public
void setEmployeeId( int
employeeId) { |
13 |
this .employeeId = employeeId; |
15 |
public
String getEmployeeName() { |
18 |
public
void setEmployeeName(String employeeName) { |
19 |
this .employeeName = employeeName; |
21 |
public
String getDepartment() { |
24 |
public
void setDepartment(String department) { |
25 |
this .department = department; |
就如你所见的,如果你需要序列化任何类,那么你
必须实现 Serializable 接口 ,这个接口是标记接口(marker interface)。
Java中的标记接口(marker interface)就是一个没有任何字段或者方法的接口,简单的来说,java中把空接口叫做标记接口(marker interface)
2.SerializeMain.java
01 |
package org.arpit.javapostsforlearning; |
02 |
import java.io.FileOutputStream; |
03 |
import java.io.IOException; |
04 |
import java.io.ObjectOutputStream; |
05 |
public
class SerializeMain { |
08 |
* @author Arpit Mandliya |
10 |
public
static void
main(String[] args) { |
12 |
Employee emp =
new Employee(); |
13 |
emp.setEmployeeId( 101 ); |
14 |
emp.setEmployeeName( "Arpit" ); |
15 |
emp.setDepartment( "CS" ); |
18 |
FileOutputStream fileOut =
new FileOutputStream( "employee.ser" ); |
19 |
ObjectOutputStream outStream =
new ObjectOutputStream(fileOut); |
20 |
outStream.writeObject(emp); |
对于反序列化:
在包src->org.arpit.javapostsforlearning中,创建 DeserializeMain.java
3.DeserializeMain.java
01 |
package org.arpit.javapostsforlearning; |
02 |
import java.io.IOException; |
03 |
import java.io.ObjectInputStream; |
05 |
public class
DeserializeMain { |
07 |
* @author Arpit Mandliya |
09 |
public
static void
main(String[] args) { |
14 |
FileInputStream fileIn = new
FileInputStream( "employee.ser" ); |
15 |
ObjectInputStream in =
new ObjectInputStream(fileIn); |
16 |
emp = (Employee) in.readObject(); |
23 |
} catch (ClassNotFoundException c) |
25 |
System.out.println( "Employee class not found" ); |
29 |
System.out.println( "Deserialized Employee..." ); |
30 |
System.out.println( "Emp id: "
+ emp.getEmployeeId()); |
31 |
System.out.println( "Name: "
+ emp.getEmployeeName()); |
32 |
System.out.println( "Department: "
+ emp.getDepartment()); |
4.运行:
首先运行SerializeMain.java,然后运行 DeserializeMain.java,你会得到如下的结果:
1 |
Deserialized Employee... |
就这样,我们序列化了一个employee对象,并对它进行反序列化。这看起来和简单,但是如果其中包含对象引用,继承,那么情况就会变得复杂。接下来让我们一个接一个的看一下例子,看看如何在各种场合中实现序列化。
案例1 - 如果对象引用了其他对象,那该如何
我们已经看过最简单的序列化例子,现在看看,如何处理对象中引用了其他对象的场合。我们该如何序列化?引用对象也会被序列化吗?对的,你不需要显式的序列化引用对象。当你序列化任何对象,如果它包含引用对象,那么Java序列化会自动序列化该对象的整个对象图。例如,Employee现在引用了一个address对象,并且Address也引用了其他对象(例如,Home),那么当你序列化Employee对象的时候,所有其他引用对象,例如address和home将会被自动地被序列化。让我们来创建Address类,并它Address的对象作为引用,添加到employee类中。
Employee.java:
01 |
package org.arpit.javapostsforlearning; |
02 |
import java.io.Serializable; |
04 |
public class
Employee implements
Serializable{ |
11 |
public
int getEmployeeId() { |
14 |
public
void setEmployeeId( int
employeeId) { |
15 |
this .employeeId = employeeId; |
17 |
public
String getEmployeeName() { |
20 |
public
void setEmployeeName(String employeeName) { |
21 |
this .employeeName = employeeName; |
23 |
public
String getDepartment() { |
26 |
public
void setDepartment(String department) { |
27 |
this .department = department; |
29 |
public
Address getAddress() { |
32 |
public
void setAddress(Address address) { |
33 |
this .address = address; |
在 org.arpit.javapostsforlearning 包中,创建Address.java
Address.java:
01 |
package org.arpit.javapostsforlearning; |
02 |
public class
Address { |
07 |
public
Address( int
homeNo, String street, String city) { |
13 |
public
int getHomeNo() { |
16 |
public
void setHomeNo( int
homeNo) { |
19 |
public
String getStreet() { |
22 |
public
void setStreet(String street) { |
25 |
public
String getCity() { |
28 |
public
void setCity(String city) { |
在包 org.arpit.javapostsforlearning中,创建SerializeDeserializeMain.java
SerializeDeserializeMain.java:
01 |
package org.arpit.javapostsforlearning; |
02 |
import java.io.FileInputStream; |
03 |
import java.io.FileOutputStream; |
04 |
import java.io.IOException; |
05 |
import java.io.ObjectInputStream; |
06 |
import java.io.ObjectOutputStream; |
08 |
public class
SerializeDeserializeMain { |
10 |
* @author Arpit Mandliya |
12 |
public
static void
main(String[] args) { |
14 |
Employee emp =
new Employee(); |
15 |
emp.setEmployeeId( 101 ); |
16 |
emp.setEmployeeName( "Arpit" ); |
17 |
emp.setDepartment( "CS" ); |
18 |
Address address= new
Address( 88 , "MG road" , "Pune" ); |
19 |
emp.setAddress(address); |
23 |
FileOutputStream fileOut =
new FileOutputStream( "employee.ser" ); |
24 |
ObjectOutputStream outStream =
new ObjectOutputStream(fileOut); |
25 |
outStream.writeObject(emp); |
37 |
FileInputStream fileIn = new
FileInputStream( "employee.ser" ); |
38 |
ObjectInputStream in =
new ObjectInputStream(fileIn); |
39 |
emp = (Employee) in.readObject(); |
46 |
} catch (ClassNotFoundException c) |
48 |
System.out.println( "Employee class not found" ); |
52 |
System.out.println( "Deserialized Employee..." ); |
53 |
System.out.println( "Emp id: "
+ emp.getEmployeeId()); |
54 |
System.out.println( "Name: "
+ emp.getEmployeeName()); |
55 |
System.out.println( "Department: "
+ emp.getDepartment()); |
56 |
address=emp.getAddress(); |
57 |
System.out.println( "City :" +address.getCity()); |
运行它:
当你运行SerializeDeserializeMain.java。你会得到这样的结果:
1 |
java.io.NotSerializableException: org.arpit.javapostsforlearning.Address |
2 |
at java.io.ObjectOutputStream.writeObject0(Unknown Source) |
3 |
at java.io.ObjectOutputStream.defaultWriteFields(Unknown Source) |
4 |
at java.io.ObjectOutputStream.writeSerialData(Unknown Source) |
5 |
at java.io.ObjectOutputStream.writeOrdinaryObject(Unknown Source) |
6 |
at java.io.ObjectOutputStream.writeObject0(Unknown Source) |
7 |
at java.io.ObjectOutputStream.writeObject(Unknown Source) |
我们将解释哪里出错了。我忘记了说,Address 类也必须是serializable。那么Address类必须继承serialzable接口。
Address.java:
01 |
import java.io.Serializable; |
03 |
public class
Address implements
Serializable{ |
08 |
public
Address( int
homeNo, String street, String city) { |
14 |
public
int getHomeNo() { |
17 |
public
void setHomeNo( int
homeNo) { |
20 |
public
String getStreet() { |
23 |
public
void setStreet(String street) { |
26 |
public
String getCity() { |
29 |
public
void setCity(String city) { |
再次运行:
当你再次运行SerializeDeserializeMain.java。你可以得到如下的结果
1 |
Deserialized Employee... |
案例2:如果我们不能访问引用对象的源代码(例如,你不能访问上面的Address类的源码)
如果我们不能访问到address类,那么我们该如何在Address类中实现serializable 接口?是否有另外的途径来实现呢?对的,你可以创建另外一个类,并继承Address,然后让它继承serializable 接口,但是对于下面的情况,这个方案会失败:
- 如果引用类被定义为final
- 如果引用类引用了另外一个非可序列化的对象
那么,我们该如何序列化Employee对象?解决的办法是,标记transient。如果你不需要序列化任何字段,只需把它标记为transient。
1 |
transient Address address |
在Employee类中,标记了address为transient之后,运行程序。你会得到nullPointerException,因为在反序列化过程中,Address引用将会是null。
案例3 - 如果我仍然需要保存引用对象的状态呢?(例如address对象)
如果你在反序列化过程中,标记了address为transient,它将会返回null结果。但是如果你仍然需要保存它的状态,你就需要序列化address对象。 Java序列化提供一个机制,如果你有特定签名的private方法,那么它们就会在序列化和反序列化过程中被调用,所以我们将重写Employee类的writeObject和readObject方法,然后它们就会在Employee对象序列化/反序列化过程中被调用。
Employee.java:
01 |
package org.arpit.javapostsforlearning; |
02 |
import java.io.IOException; |
03 |
import java.io.ObjectInputStream; |
04 |
import java.io.ObjectOutputStream; |
05 |
import java.io.Serializable; |
07 |
public class
Employee implements
Serializable{ |
12 |
transient
Address address; |
14 |
public
int getEmployeeId() { |
17 |
public
void setEmployeeId( int
employeeId) { |
18 |
this .employeeId = employeeId; |
20 |
public
String getEmployeeName() { |
23 |
public
void setEmployeeName(String employeeName) { |
24 |
this .employeeName = employeeName; |
26 |
public
String getDepartment() { |
29 |
public
void setDepartment(String department) { |
30 |
this .department = department; |
32 |
public
Address getAddress() { |
35 |
public
void setAddress(Address address) { |
36 |
this .address = address; |
39 |
private
void writeObject(ObjectOutputStream os)
throws IOException, ClassNotFoundException |
42 |
os.defaultWriteObject(); |
43 |
os.writeInt(address.getHomeNo()); |
44 |
os.writeObject(address.getStreet()); |
45 |
os.writeObject(address.getCity()); |
48 |
{ e.printStackTrace(); } |
51 |
private
void readObject(ObjectInputStream is)
throws IOException, ClassNotFoundException |
54 |
is.defaultReadObject(); |
55 |
int
homeNo=is.readInt(); |
56 |
String street=(String) is.readObject(); |
57 |
String city=(String) is.readObject(); |
58 |
address= new
Address(homeNo,street,city); |
60 |
} catch
(Exception e) { e.printStackTrace(); } |
另外有一点需要牢记的,ObjectInputStream读取数据的顺序和ObjectOutputStream写入数据的顺序是一致的.
在包org.arpit.javapostsforlearning 中创建Address.java
Address.java:
01 |
package org.arpit.javapostsforlearning; |
02 |
import java.io.Serializable; |
04 |
public class
Address { |
11 |
public
Address( int
homeNo, String street, String city) { |
17 |
public
int getHomeNo() { |
20 |
public
void setHomeNo( int
homeNo) { |
23 |
public
String getStreet() { |
26 |
public
void setStreet(String street) { |
29 |
public
String getCity() { |
32 |
public
void setCity(String city) { |
在包org.arpit.javapostsforlearning中创建SerializeDeserializeMain.java
SerializeDeserializeMain.java:
01 |
package org.arpit.javapostsforlearning; |
02 |
import java.io.FileInputStream; |
03 |
import java.io.FileOutputStream; |
04 |
import java.io.IOException; |
05 |
import java.io.ObjectInputStream; |
06 |
import java.io.ObjectOutputStream; |
08 |
public class
SerializeDeserializeMain { |
10 |
* @author Arpit Mandliya |
12 |
public
static void
main(String[] args) { |
14 |
Employee emp =
new Employee(); |
15 |
emp.setEmployeeId( 101 ); |
16 |
emp.setEmployeeName( "Arpit" ); |
17 |
emp.setDepartment( "CS" ); |
18 |
Address address= new
Address( 88 , "MG road" , "Pune" ); |
19 |
emp.setAddress(address); |
23 |
FileOutputStream fileOut =
new FileOutputStream( "employee.ser" ); |
24 |
ObjectOutputStream outStream =
new ObjectOutputStream(fileOut); |
25 |
outStream.writeObject(emp); |
37 |
FileInputStream fileIn = new
FileInputStream( "employee.ser" ); |
38 |
ObjectInputStream in =
new ObjectInputStream(fileIn); |
39 |
emp = (Employee) in.readObject(); |
46 |
} catch (ClassNotFoundException c) |
48 |
System.out.println( "Employee class not found" ); |
52 |
System.out.println( "Deserialized Employee..." ); |
53 |
System.out.println( "Emp id: "
+ emp.getEmployeeId()); |
54 |
System.out.println( "Name: "
+ emp.getEmployeeName()); |
55 |
System.out.println( "Department: "
+ emp.getDepartment()); |
56 |
address=emp.getAddress(); |
57 |
System.out.println( "City :" +address.getCity()); |
运行 :
当你运行SerializeDeserializeMain.java.你会得到如下的结果:
1 |
Deserialized Employee... |
现在我们就得到了address对象的状态,就如它被序列化前的一样。
序列化中的继承:
现在我们看看继承是如何影响序列化的。不管父类是不是可序列化,这将引出很多个例子。如果父类是非可序列化的,我们将如何处理,并且它是如何工作的。让我们看看例子。
我们将创建一个Person.java,作为 Employee的父类。
案例4: 如果父类是可序列化的
如果父类可序列化,那么所有的继承类将是可序列化的。
案例5: 如果父类为非可序列化呢?
如果父类为非可序列化的 ,那么我们的处理办法会很不一样。
- 如果父类为非可序列化的,那么它必然不会有参数构造函数。
Person.java
01 |
package org.arpit.javapostsforlearning; |
04 |
String name= "default" ; |
09 |
System.out.println( "Person:Constructor" ); |
12 |
public
Person(String name, String nationality) { |
15 |
this .nationality = nationality; |
18 |
public
String getName() { |
22 |
public
void setName(String name) { |
26 |
public
String getNationality() { |
30 |
public
void setNationality(String nationality) { |
31 |
this .nationality = nationality; |
在包org.arpit.javapostsforlearning 中创建Employee.java
Employee.java:
01 |
package org.arpit.javapostsforlearning; |
02 |
import java.io.Serializable; |
04 |
public class
Employee extends
Person implements Serializable{ |
09 |
public
Employee( int
employeeId,String name,String department,String nationality) |
11 |
super (name,nationality); |
12 |
this .employeeId=employeeId; |
13 |
this .department=department; |
14 |
System.out.println( "Employee:Constructor" ); |
17 |
public
int getEmployeeId() { |
20 |
public
void setEmployeeId( int
employeeId) { |
21 |
this .employeeId = employeeId; |
24 |
public
String getDepartment() { |
27 |
public
void setDepartment(String department) { |
28 |
this .department = department; |
在org.arpit.javapostsforlearning包中创建SerializeDeserializeMain.java
SerializeDeserializeMain.java:
01 |
package org.arpit.javapostsforlearning; |
02 |
import java.io.FileInputStream; |
03 |
import java.io.FileOutputStream; |
04 |
import java.io.IOException; |
05 |
import java.io.ObjectInputStream; |
06 |
import java.io.ObjectOutputStream; |
08 |
public class
SerializeDeserializeMain { |
11 |
* @author Arpit Mandliya |
13 |
public
static void
main(String[] args) { |
16 |
Employee emp =
new Employee( 101 , "Arpit" , "CS" , "Indian" ); |
17 |
System.out.println( "Before serializing" ); |
18 |
System.out.println( "Emp id: "
+ emp.getEmployeeId()); |
19 |
System.out.println( "Name: "
+ emp.getName()); |
20 |
System.out.println( "Department: "
+ emp.getDepartment()); |
21 |
System.out.println( "Nationality: "
+ emp.getNationality()); |
22 |
System.out.println( "************" ); |
23 |
System.out.println( "Serializing" ); |
26 |
FileOutputStream fileOut =
new FileOutputStream( "employee.ser" ); |
27 |
ObjectOutputStream outStream =
new ObjectOutputStream(fileOut); |
28 |
outStream.writeObject(emp); |
37 |
System.out.println( "************" ); |
38 |
System.out.println( "Deserializing" ); |
42 |
FileInputStream fileIn = new
FileInputStream( "employee.ser" ); |
43 |
ObjectInputStream in =
new ObjectInputStream(fileIn); |
44 |
emp = (Employee) in.readObject(); |
51 |
} catch (ClassNotFoundException c) |
53 |
System.out.println( "Employee class not found" ); |
57 |
System.out.println( "After serializing" ); |
58 |
System.out.println( "Emp id: "
+ emp.getEmployeeId()); |
59 |
System.out.println( "Name: "
+ emp.getName()); |
60 |
System.out.println( "Department: "
+ emp.getDepartment()); |
61 |
System.out.println( "Nationality: "
+ emp.getNationality()); |
运行:
当你运行SerializeDeserializeMain.java后,你会得到如下的输出,如果父类是非可序列化的,那么在反序列化过程中,所有继承于父类的实例变量值,将会通过调用非序列化构造函数来初始化。 这里 name继承于person,所以在反序列化过程中,name将会被初始化为默认值。
案例6 - 如果父类是可序列化,但你不需要继承类为可序列化
如果你不希望继承类为可序列化,那么你需要实现 writeObject() 和readObject() 方法,并且需要抛出NotSerializableException 异常。
案例7 - 可否序列化静态变量?
不能。因为静态变量是类级别的,不是对象级别的,当你序列化一个对象的时候,是不能序列化静态变量。
总结:
- 序列化是java对象的values/states转化为字节并在网络中传输或者保存它的过程。另外反序列化是把字节码翻译为对应的对象的过程。
- 序列化的好处是,JVM的独立性,也就是说,一个对象可以在一个平台中被序列化,然后在另外一个不同的平台反序列化。
- 如果你需要序列化任何对象,你必须实现标记接口Serializable。
- Java中的标记接口(Marker interface)就是没有字段或者方法的接口,或者更简单的说,空接口
- serialVersionUID 是用于保证同一个对象(在序列化中会被用到)可以在Deserialization过程中被载入。serialVersionUID 是用于对象的版本控制。
- 当你需要序列化任何包含引用对象的对象,那么Java会自动序列化该对象的整个对象图。
- 如果你不希望序列化某个字段,你可以标记它为trasient
- 如果父类为可序列化,那么它的继承类也将是可序列化的。
- 如果父类为非可序列化,那么在反序列化过程中,所有继承于父类的实例变量值将会通过调用非可序列化的构造器来初始化。
- 如果你需希望子类为可序列化的,那么你需要实现writeObject() 和 readObject() 方法,并在这两个方法中抛出NotSerializableException异常
- 你不能序列化静态变量。