提到Java序列化,相信大家都不陌生。我们在序列化的时候,需要将被序列化的类实现Serializable接口,这样的类在序列化时,会默认将所有的字段都序列化。那么当我们在序列化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
|
import
java.io.Serializable;
import
java.util.Date;
public
class
LoginInfo
implements
Serializable {
private
static
final
long
serialVersionUID = 8364988832581114038L;
private
String userName;
private
transient
String password;
//Note this key word "transient"
private
Date loginDate;
//Default Public Constructor
public
LoginInfo() {
System.out.println(
"LoginInfo Constructor"
);
}
//Non-Default constructor
public
LoginInfo(String username, String password) {
this
.userName = username;
this
.password = password;
loginDate =
new
Date();
}
public
String toString() {
return
"UserName="
+ userName +
", Password="
+ password +
", LoginDate="
+ loginDate;
}
}
|
测试类:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
import
java.io.FileInputStream;
import
java.io.FileOutputStream;
import
java.io.ObjectInputStream;
import
java.io.ObjectOutputStream;
public
class
Test {
static
String fileName =
"C:/x.file"
;
public
static
void
main(String[] args)
throws
Exception {
LoginInfo info =
new
LoginInfo(
"name"
,
"123"
);
System.out.println(info);
//Write
System.out.println(
"Serialize object"
);
ObjectOutputStream oos =
new
ObjectOutputStream(
new
FileOutputStream(fileName));
oos.writeObject(info);
oos.close();
//Read
System.out.println(
"Deserialize object."
);
ObjectInputStream ois =
new
ObjectInputStream(
new
FileInputStream(fileName));
LoginInfo info2 = (LoginInfo)ois.readObject();
ois.close();
System.out.println(info2);
}
}
|
执行结果:
1
2
3
4
|
UserName=name, Password=
123
, LoginDate=Wed Nov
04
16
:
41
:
49
CST
2015
Serialize object
Deserialize object.
UserName=name, Password=
null
, LoginDate=Wed Nov
04
16
:
41
:
49
CST
2015
|
另一种可以达到此目的的方法可能就比较少用了,那就是——不实现Serializable而实现Externalizable接口。这个Externalizable接口有两个方法,分别表示在序列化的时候需要序列化哪些字段和反序列化的时候能够反序列化哪些字段:
1
2
|
void
writeExternal(ObjectOutput out)
throws
IOException;
void
readExternal(ObjectInput in)
throws
IOException, ClassNotFoundException;
|
于是就有了下面的代码:
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
|
import
java.io.Externalizable;
import
java.io.IOException;
import
java.io.ObjectInput;
import
java.io.ObjectOutput;
import
java.util.Date;
public
class
LoginInfo2
implements
Externalizable {
private
static
final
long
serialVersionUID = 8364988832581114038L;
private
String userName;
private
String password;
private
Date loginDate;
//Default Public Constructor
public
LoginInfo2() {
System.out.println(
"LoginInfo Constructor"
);
}
//Non-Default constructor
public
LoginInfo2(String username, String password) {
this
.userName = username;
this
.password = password;
loginDate =
new
Date();
}
public
String toString() {
return
"UserName="
+ userName +
", Password="
+ password +
", LoginDate="
+ loginDate;
}
@Override
public
void
writeExternal(ObjectOutput out)
throws
IOException {
System.out.println(
"Externalizable.writeExternal(ObjectOutput out) is called"
);
out.writeObject(loginDate);
out.writeUTF(userName);
}
@Override
public
void
readExternal(ObjectInput in)
throws
IOException, ClassNotFoundException {
System.out.println(
"Externalizable.readExternal(ObjectInput in) is called"
);
loginDate = (Date)in.readObject();
userName = (String)in.readUTF();
}
}
|
测试类除了类名使用LoginInfo2以外,其他保持不变。下面是执行结果:
1
2
3
4
5
6
7
|
UserName=name, Password=
123
, LoginDate=Wed Nov
04
16
:
36
:
39
CST
2015
Serialize object
Externalizable.writeExternal(ObjectOutput out) is called
Deserialize object.
LoginInfo Constructor
//-------------------------Note this line
Externalizable.readExternal(ObjectInput in) is called
UserName=name, Password=
null
, LoginDate=Wed Nov
04
16
:
36
:
39
CST
2015
|
可以看到,反序列化后的Password一项依然为null。
需要注意的是:对于恢复Serializable对象,对象完全以它存储的二进制为基础来构造,而不调用构造器。而对于一个Externalizable对象,public的无参构造器将会被调用(因此你可以看到上面的测试结果中有LoginInfoConstructor这一行),之后再调用readExternal()方法。在Externalizable接口文档中,也给出了相关描述:
When an Externalizable object is reconstructed, an instance is created using the public no-arg constructor, then the readExternal method called. Serializable objects are restored by reading them from an ObjectInputStream.
如果没有发现public的无参构造器,那么将会报错。(把LoginInfo2类的无参构造器注释掉,就会产生错误了):
1
2
3
4
5
6
7
8
9
10
|
UserName=name, Password=
123
, LoginDate=Wed Nov
04
17
:
03
:
24
CST
2015
Serialize object
Deserialize object.
Exception in thread
"main"
java.io.InvalidClassException: LoginInfo2; no valid constructor
at java.io.ObjectStreamClass$ExceptionInfo.newInvalidClassException(ObjectStreamClass.java:
150
)
at java.io.ObjectStreamClass.checkDeserialize(ObjectStreamClass.java:
768
)
at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:
1772
)
at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:
1350
)
at java.io.ObjectInputStream.readObject(ObjectInputStream.java:
370
)
at Test2.main(Test2.java:
19
)
|
那么,如果把Externalizable接口和transient关键字一起用,会是什么效果呢?我们在LoginInfo2中的password加上关键字transient,再修改writeExternal()和readExternal()方法:
1
2
3
4
5
6
7
8
9
10
11
12
13
|
@Override
public
void
writeExternal(ObjectOutput out)
throws
IOException {
out.writeObject(loginDate);
out.writeUTF(userName);
out.writeUTF(password);
//强行将transient修饰的password属性也序列化进去
}
@Override
public
void
readExternal(ObjectInput in)
throws
IOException, ClassNotFoundException {
loginDate = (Date)in.readObject();
userName = (String)in.readUTF();
password = (String)in.readUTF();
//反序列化password字段
}
|
执行结果:
1
2
3
4
5
|
UserName=name, Password=
123
, LoginDate=Wed Nov
04
16
:
58
:
27
CST
2015
Serialize object
Deserialize object.
LoginInfo Constructor
UserName=name, Password=
123
, LoginDate=Wed Nov
04
16
:
58
:
27
CST
2015
|
从结果中可以看到,尽管在password字段上使用了transient关键字,但是这还是没能阻止被序列化。因为不是以Serializable方式去序列化和反序列化的。也就是说:transient关键字只能与Serializable接口搭配使用。