本文着重讲解一下Java序列化的相关内容。
如果对Java序列化感兴趣的同学可以研究一下。
一.Java序列化的作用
有的时候我们想要把一个Java对象变成字节流的形式传出去,有的时候我们想要从一个字节流中恢复一个Java对象。例如,有的时候我们想要
把一个Java对象写入到硬盘或者传输到网路上面的其它计算机,这时我们就需要自己去通过java把相应的对象写成转换成字节流。对于这种通用
的操作,我们为什么不使用统一的格式呢?没错,这里就出现了java的序列化的概念。在Java的OutputStream类下面的子类ObjectOutput-
Stream类就有对应的WriteObject(Object object) 其中要求对应的object实现了java的序列化的接口。
为了更好的理解java序列化的应用,我举两个自己在开发项目中遇到的例子:
1)在使用tomcat开发JavaEE相关项目的时候,我们关闭tomcat后,相应的session中的对象就存储在了硬盘上,如果我们想要在tomcat重启的
时候能够从tomcat上面读取对应session中的内容,那么保存在session中的内容就必须实现相关的序列化操作。
2)如果我们使用的java对象要在分布式中使用或者在rmi远程调用的网络中使用的话,那么相关的对象必须实现java序列化接口。
亲爱的小伙伴,大概你已经了解了java序列化相关的作用,接下来们来看看如何实现java的序列化吧。~
二.实现java对象的序列化和反序列化。
Java对象的序列化有两种方式。
a.是相应的对象实现了序列化接口Serializable,这个使用的比较多,对于序列化接口Serializable接口是一个空的接口,它的主要作用就是
标识这个对象时可序列化的,jre对象在传输对象的时候会进行相关的封装。这里就不做过多的介绍了。
下面是一个实现序列化接口的Java序列化的例子:非常简单
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
|
package
com.shop.domain;
import
java.util.Date;
public
class
Article
implements
java.io.Serializable {
private
static
final
long
serialVersionUID = 1L;
private
Integer id;
private
String title;
//文章标题
private
String content;
// 文章内容
private
String faceIcon;
//表情图标
private
Date postTime;
//文章发表的时间
private
String ipAddr;
//用户的ip
private
User author;
//回复的用户
public
Integer getId() {
return
id;
}
public
void
setId(Integer id) {
this
.id = id;
}
public
String getTitle() {
return
title;
}
public
void
setTitle(String title) {
this
.title = title;
}
public
String getContent() {
return
content;
}
public
void
setContent(String content) {
this
.content = content;
}
public
String getFaceIcon() {
return
faceIcon;
}
public
void
setFaceIcon(String faceIcon) {
this
.faceIcon = faceIcon;
}
public
Date getPostTime() {
return
postTime;
}
public
void
setPostTime(Date postTime) {
this
.postTime = postTime;
}
public
User getAuthor() {
return
author;
}
public
void
setAuthor(User author) {
this
.author = author;
}
public
String getIpAddr() {
return
ipAddr;
}
public
void
setIpAddr(String ipAddr) {
this
.ipAddr = ipAddr;
}
}
|
b.实现序列化的第二种方式为实现接口Externalizable,Externlizable的部分源代码如下:
1
2
3
4
5
6
7
8
|
*
@see
java.io.ObjectInput
*
@see
java.io.Serializable
*
@since
JDK1.
1
*/
public
interface
Externalizable
extends
java.io.Serializable {
/**
* The object
implements
the writeExternal method to save its contents
* by calling the methods of DataOutput
for
its primitive values or
|
没错,Externlizable接口继承了java的序列化接口,并增加了两个方法:
void writeExternal(ObjectOutput out) throws IOException;
void readExternal(ObjectInput in) throws IOException, ClassNotFoundException;
首先,我们在序列化对象的时候,由于这个类实现了Externalizable 接口,在writeExternal()方法里定义了哪些属性可以序列化,
哪些不可以序列化,所以,对象在经过这里就把规定能被序列化的序列化保存文件,不能序列化的不处理,然后在反序列的时候自动调
用readExternal()方法,根据序列顺序挨个读取进行反序列,并自动封装成对象返回,然后在测试类接收,就完成了反序列。
所以说Exterinable的是Serializable的一个扩展。
为了更好的理解相关内容,请看下面的例子:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
|
package
com.xiaohao.test;
import
java.io.Externalizable;
import
java.io.FileInputStream;
import
java.io.FileNotFoundException;
import
java.io.FileOutputStream;
import
java.io.IOException;
import
java.io.ObjectInput;
import
java.io.ObjectInputStream;
import
java.io.ObjectOutput;
import
java.io.ObjectOutputStream;
import
java.text.SimpleDateFormat;
import
java.util.Date;
/**
* 测试实体类
* @author 小浩
* @创建日期 2015-3-12
*/
class
Person
implements
Externalizable{
private
static
final
long
serialVersionUID = 1L;
String password;
String age;
public
Person(String userName, String password, String age) {
super
();
this
.userName = userName;
this
.password = password;
this
.age = age;
}
public
Person() {
super
();
}
public
String getAge() {
return
age;
}
public
void
setAge(String age) {
this
.age = age;
}
public
String getUserName() {
return
userName;
}
public
void
setUserName(String userName) {
this
.userName = userName;
}
public
String getPassword() {
return
password;
}
public
void
setPassword(String password) {
this
.password = password;
}
/**
* 序列化操作的扩展类
*/
@Override
public
void
writeExternal(ObjectOutput out)
throws
IOException {
//增加一个新的对象
Date date=
new
Date();
out.writeObject(userName);
out.writeObject(password);
out.writeObject(date);
}
/**
* 反序列化的扩展类
*/
@Override
public
void
readExternal(ObjectInput in)
throws
IOException,
ClassNotFoundException {
//注意这里的接受顺序是有限制的哦,否则的话会出错的
// 例如上面先write的是A对象的话,那么下面先接受的也一定是A对象...
userName=(String) in.readObject();
password=(String) in.readObject();
SimpleDateFormat sdf=
new
SimpleDateFormat(
"yyyy-MM-dd"
);
Date date=(Date)in.readObject();
System.out.println(
"反序列化后的日期为:"
+sdf.format(date));
}
@Override
public
String toString() {
//注意这里的年龄是不会被序列化的,所以在反序列化的时候是读取不到数据的
return
"用户名:"
+userName+
"密 码:"
+password+
"年龄:"
+age;
}
}
/**
* 序列化和反序列化的相关操作类
* @author 小浩
* @创建日期 2015-3-12
*/
class
Operate{
/**
* 序列化方法
* @throws IOException
* @throws FileNotFoundException
*/
public
void
serializable(Person person)
throws
FileNotFoundException, IOException{
ObjectOutputStream outputStream=
new
ObjectOutputStream(
new
FileOutputStream(
"a.txt"
));
outputStream.writeObject(person);
}
/**
* 反序列化的方法
* @throws IOException
* @throws FileNotFoundException
* @throws ClassNotFoundException
*/
public
Person deSerializable()
throws
FileNotFoundException, IOException, ClassNotFoundException{
ObjectInputStream ois=
new
ObjectInputStream(
new
FileInputStream(
"a.txt"
));
return
(Person) ois.readObject();
}
}
/**
* 测试实体主类
* @author 小浩
* @创建日期 2015-3-12
*/
public
class
Test{
public
static
void
main(String[] args)
throws
FileNotFoundException, IOException, ClassNotFoundException {
Operate operate=
new
Operate();
Person person=
new
Person(
"小浩"
,
"123456"
,
"20"
);
System.out.println(
"为序列化之前的相关数据如下:\n"
+person.toString());
operate.serializable(person);
Person newPerson=operate.deSerializable();
System.out.println(
"-------------------------------------------------------"
);
System.out.println(
"序列化之后的相关数据如下:\n"
+newPerson.toString());
}
}
|
首先,我们在序列化UserInfo对象的时候,由于这个类实现了Externalizable 接口,在writeExternal()方法里定义了哪些属性可
以序列化,哪些不可以序列化,所以,对象在经过这里就把规定能被序列化的序列化保存文件,不能序列化的不处理,然后在反序列
的时候自动调用readExternal()方法,根据序列顺序挨个读取进行反序列,并自动封装成对象返回,然后在测试类接收,就完成了反
序列。
***对于实现Java的序列化接口需要注意一下几点:
1.java中的序列化时transient变量(这个关键字的作用就是告知JAVA我不可以被序列化)和静态变量不会被序列
化(下面是一个测试的例子)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
|
import
java.io.*;
class
Student1
implements
Serializable {
private
static
final
long
serialVersionUID = 1L;
private
String name;
private
transient
String password;
private
static
int
count =
0
;
public
Student1(String name, String password) {
System.out.println(
"调用Student的带参的构造方法"
);
this
.name = name;
this
.password = password;
count++;
}
public
String toString() {
return
"人数: "
+ count +
" 姓名: "
+ name +
" 密码: "
+ password;
}
}
public
class
ObjectSerTest1 {
public
static
void
main(String args[]) {
try
{
FileOutputStream fos =
new
FileOutputStream(
"test.obj"
);
ObjectOutputStream oos =
new
ObjectOutputStream(fos);
Student1 s1 =
new
Student1(
"张三"
,
"12345"
);
Student1 s2 =
new
Student1(
"王五"
,
"54321"
);
oos.writeObject(s1);
oos.writeObject(s2);
oos.close();
FileInputStream fis =
new
FileInputStream(
"test.obj"
);
ObjectInputStream ois =
new
ObjectInputStream(fis);
Student1 s3 = (Student1) ois.readObject();
Student1 s4 = (Student1) ois.readObject();
System.out.println(s3);
System.out.println(s4);
ois.close();
}
catch
(IOException e) {
e.printStackTrace();
}
catch
(ClassNotFoundException e1) {
e1.printStackTrace();
}
}
}
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
|
import
java.io.FileInputStream;
import
java.io.FileOutputStream;
import
java.io.IOException;
import
java.io.ObjectInputStream;
import
java.io.ObjectOutputStream;
public
class
Test{
public
static
void
main(String args[]){
try
{
FileInputStream fis =
new
FileInputStream(
"test.obj"
);
ObjectInputStream ois =
new
ObjectInputStream(fis);
Student1 s3 = (Student1) ois.readObject();
Student1 s4 = (Student1) ois.readObject();
System.out.println(s3);
System.out.println(s4);
ois.close();
}
catch
(IOException e) {
e.printStackTrace();
}
catch
(ClassNotFoundException e1) {
e1.printStackTrace();
}
}
}
|
2.也是最应该注意的,如果你先序列化对象A后序列化B,那么在反序列化的时候一定记着JAVA规定先读到的对象
是先被序列化的对象,不要先接收对象B,那样会报错.尤其在使用上面的Externalizable的时候一定要注意读取
的先后顺序。
3.实现序列化接口的对象并不强制声明唯一的serialVersionUID,是否声明serialVersionUID对于对象序列化的向
上向下的兼容性有很大的影响。我们来做个测试:
把User中的serialVersionUID去掉,序列化保存。反序列化的时候,增加或减少个字段,看是否成功。
保存到文件中:
增加或者减少字段后,从文件中读出来,反序列化:
结果:抛出异常信息
Java代码