上一篇中我们讲述了对象的序列化与对象的输入输出流,这一篇章我们讲述 对象序列化的transient 关键字。
一、transient关键字
transient关键字可以使被修饰的属性,将不会被Java默认的方式序列化和反序列化,如果要对transient关键字修饰的属性进行序列化和反序列化,需要在类中实现writeObject方法和readObject方法。
package com.bxp.serializer;
import java.io.Serializable;
public class Student implements Serializable {
private static final long serialVersionUID = -1242608031152179018L;
private String name;
private transient String id;
private Integer age;
public Student() {
}
public Student(String name, String id, Integer age) {
this.name = name;
this.id = id;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", id='" + id + '\'' +
", age=" + age +
'}';
}
}
对象输出和输入方法:
public static void outputObject() throws IOException {
Student student = new Student("bxp", "12306", 25);
FileOutputStream fop = new FileOutputStream("student.dat");
ObjectOutputStream oos = new ObjectOutputStream(fop);
oos.writeObject(student);
oos.flush();
oos.close();
}
public static void inputObject() throws IOException, ClassNotFoundException {
FileInputStream fis = new FileInputStream("student.dat");
ObjectInputStream ois = new ObjectInputStream(fis);
Student stu = (Student)ois.readObject();
System.out.println(stu);
fis.close();
}
我们执行outputObject(),然后再执行inputObject()方法,会输出如下内容:
Student{name='bxp', id='null', age=25}
这里就可以看出,id属性没有参与序列化和反序列化。
二、自定义序列化过程
transient关键字可以使被修饰的属性,将不会被Java默认的方式序列化和反序列化。但是
Java并不强求用户非要使用默认的序列化方式,用户也可以按照自己的喜好自己指定自己想要的序列化方式----只要你自己能保证序列化前后能得到想要的数据就好了。手动指定序列化方式的规则是:
进 行序列化、反序列化时,虚拟机会首先试图调用对象里的 writeObject 和 readObject 方法,进行用户自定义的序列化和反序列化。如果没有这 样的方法,那么默认调用的是ObjectOutputStream的defaultWriteObject以及ObjectInputStream的 defaultReadObject方法。换言之,利用自定义的writeObject方法和readObject方法,用户可以自己控制序列化和反序列 化的过程。
这是非常有用的。比如:
(1)、有些 场景下,某些字段我们并不想要使用Java提供给我们的序列化方式,而是想要以自定义的方式去序列化它,比如ArrayList的 elementData,elementData是一个数组,假如数组的大小是20,但是真正的有效值是2个,其余都是null,如果使用默认的序列化,数组的20个元素都会被序列化,这就浪费了时间和存储资源,所以对其使用自定义的序列化,只序列化那两个有效值,ArrayList的序列化方法如下:
private void writeObject(java.io.ObjectOutputStream s)
throws java.io.IOException{
// Write out element count, and any hidden stuff
int expectedModCount = modCount;
s.defaultWriteObject();
// Write out size as capacity for behavioural compatibility with clone()
s.writeInt(size);
// Write out all elements in the proper order.
for (int i=0; i<size; i++) {
s.writeObject(elementData[i]);
}
if (modCount != expectedModCount) {
throw new ConcurrentModificationException();
}
}
(2)、因为 序列化并不安全,因此有些场景下我们需要对一些敏感字段进行加密再序列化,然后再反序列化的时候按照同样的方式进行解密,就在一定程度上保证了安全性了。 要这么做,就必须自己写writeObject和readObject,writeObject方法在序列化前对字段加密,readObject方法在序 列化之后对字段解密
writeObject和readObject的通常用法:
三、自定义序列化实现
writeObject和readObject的实现很简单,实现模板如下:
private void writeObject(java.io.ObjectOutputStream s)
throws java.io.IOException{
// 先调用默认的序列化方式,将没有被transient修饰的属性进行序列化
s.defaultWriteObject();
// 自定义序列化,其实就是调用 输出流对象 s 将被transient修饰的属性输出
……
}
private void readObject(java.io.ObjectInputStream s)
throws java.io.IOException, ClassNotFoundException {
// 先调用默认的反序列化方式,将没有被transient修饰的属性进行反序列化
s.defaultReadObject();
// 自定义反序列化,其实就是调用 输入流对象 s 将被transient修饰的属性读取进来
……
}
writeObject和readObject的实现顺序要相互对应,比如,第一步都是默认的方法,第二步都处理name字段,第三部都处理age字段等。
示例:
在上面代码的基础上,在Student类中添加如下方法:
private void writeObject(java.io.ObjectOutputStream s)
throws java.io.IOException{
// 先调用默认的序列化方式,将没有被transient修饰的属性进行序列化
s.defaultWriteObject();
// 自定义序列化,其实就是调用 输出流对象 s 将被transient修饰的属性输出
s.writeUTF(id);
}
private void readObject(java.io.ObjectInputStream s)
throws java.io.IOException, ClassNotFoundException {
// 先调用默认的反序列化方式,将没有被transient修饰的属性进行反序列化
s.defaultReadObject();
// 自定义反序列化,其实就是调用 输入流对象 s 将被transient修饰的属性读取进来\
id = s.readUTF();
}
运行之后,会发现“id”字段可以序列化。