自定义序列化
在一些情况下,如果某个类的一些属性不希望被序列化,或者没有实现Serializable接口又不希望在序列化时报错,可以在属性前面加上transient关键字,Java程序在序列化时会忽略该属性
代码1-1
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
class Book implements Serializable {
private String name;
private transient int num;
public Book(String name, int num) {
super();
this.name = name;
this.num = num;
}
public String getName() {
return name;
}
public int getNum() {
return num;
}
@Override
public String toString() {
return "Book [name=" + name + ", num=" + num + "]";
}
}
public class TransientTest {
public static void main(String[] args) {
if (args == null || args.length == 0) {
throw new RuntimeException("请输入对象序列化路径");
}
ObjectOutputStream oos = null;
ObjectInputStream ois = null;
try {
oos = new ObjectOutputStream(new FileOutputStream(args[0]));
ois = new ObjectInputStream(new FileInputStream(args[0]));
Book book = new Book("红楼梦", 50);
System.out.println(book);
oos.writeObject(book);
book = (Book) ois.readObject();
System.out.println(book);
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
} finally {
try {
if (oos != null) {
oos.close();
}
if (ois != null) {
ois.close();
}
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
代码1-1运行结果:
root@lejian:/home/software/.io# touch object root@lejian:/home/software/.io# java TransientTest object Book [name=红楼梦, num=50] Book [name=红楼梦, num=0]
如代码1-1运行结果所示,将age标记为transient,在序列化时则可忽略该变量的值,所以在反序列化时,age的值为0
Java还提供了另一种自定义序列化机制,通过这种自定义序列化机制可以让程序控制如何序列化各属性,甚至完全不序列化某些属性。在序列化和反序列化过程中,需要特殊处理的类应该用如下特殊签名的方法,这些特殊的方法用以自定义序列化:
以上方法的访问权限必须为private,否则序列化和反序列化时不会调用如上方法
writeObject()方法负责写入特定类的实例状态,以便相应的readObject()方法可以恢复它。通过重写该方法,程序员可以完全获得对序列化机制的控制,程序员可以自主决定哪些属性需要序列化,或者在序列化前对一些字段做操作后再进行序列化
readObject()方法负责从流中读取并恢复对象属性,通过重写该方法,程序员可以完全获得对反序列化机制的控制,可以自主决定哪些属性可以反序列化,以及反序列化后可以再进行其他操作
代码1-2
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
public class Student implements Serializable {
private String name;
private transient int age;
public Student(String name, int age) {
super();
System.out.println("构造Student对象");
this.name = name;
this.age = age;
}
private void writeObject(ObjectOutputStream out) throws IOException {
out.writeObject(new StringBuffer(name).reverse());
out.writeInt(age);
}
private void readObject(ObjectInputStream in) throws ClassNotFoundException, IOException {
this.name = in.readObject().toString();
this.age = in.readInt() + 5;
}
@Override
public String toString() {
return "Student [name=" + name + ", age=" + age + "]";
}
public static void main(String[] args) {
if (args == null || args.length == 0) {
throw new RuntimeException("请输入对象序列化路径");
}
ObjectOutputStream oos = null;
ObjectInputStream ois = null;
try {
oos = new ObjectOutputStream(new FileOutputStream(args[0]));
ois = new ObjectInputStream(new FileInputStream(args[0]));
Student student = new Student("Amy", 18);
System.out.println(student);
oos.writeObject(student);
student = (Student) ois.readObject();
System.out.println(student);
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
} finally {
try {
if (oos != null) {
oos.close();
}
if (ois != null) {
ois.close();
}
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
代码1-2运行结果:
root@lejian:/home/software/.io# java Student student 构造Student对象 Student [name=Amy, age=18] Student [name=ymA, age=23]
如代码1-2,在序列化student对象时,对name的值进行逆序再序列化,虽然代码中对age变量做了transient的标记,但还是在序列化该对象时把age序列化到流中,而在反序列化时又对age加5,所以student对象在序列化前后,字段的属性不再一致
writeReplace()方法可以在序列化对象时将该对象替换成其他对象,该方法可以设为private、protected或者包私有等访问权限,代码1-3展示了在序列化teacher对象时替换成List对象
代码1-3
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.ObjectStreamException;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
public class Teacher implements Serializable {
private String name;
private int age;
public Teacher(String name, int age) {
super();
this.name = name;
this.age = age;
}
@Override
public String toString() {
return "Teacher [name=" + name + ", age=" + age + "]";
}
private Object writeReplace() throws ObjectStreamException {
List<Object> list = new ArrayList<Object>();
list.add("name:" + name);
list.add("age:" + age);
return list;
}
public static void main(String[] args) {
if (args == null || args.length == 0) {
throw new RuntimeException("请输入对象序列化路径");
}
ObjectOutputStream oos = null;
ObjectInputStream ois = null;
try {
oos = new ObjectOutputStream(new FileOutputStream(args[0]));
ois = new ObjectInputStream(new FileInputStream(args[0]));
Teacher teacher = new Teacher("王老师", 35);
System.out.println(teacher);
oos.writeObject(teacher);
Object obj = ois.readObject();
System.out.println(obj.getClass());
System.out.println(obj);
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
} finally {
try {
if (oos != null) {
oos.close();
}
if (ois != null) {
ois.close();
}
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
代码1-3运行结果:
root@lejian:/home/software/.io# java Teacher teacher Teacher [name=王老师, age=35] class java.util.ArrayList [name:王老师, age:35]
程序调用被序列化对象的writeReplace()方法时,如果该方法返回另一个对象,系统将再次调用另一个对象的writeReplace()方法,直到该方法不再返回另一个对象为止
readResolve()方法在序列化单例类、枚举类尤其有用,如果使用Java5提供的enum来定义枚举类则不用担心,如代码1-4就是一个枚举类
代码1-4
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
public class Orientation implements Serializable {
public static final Orientation HORIZONTAL = new Orientation(1);
public static final Orientation VERTICAL = new Orientation(2);
private int value;
private Orientation(int value) {
super();
this.value = value;
}
public static void main(String[] args) {
if (args == null || args.length == 0) {
throw new RuntimeException("请输入对象序列化路径");
}
ObjectOutputStream oos = null;
ObjectInputStream ois = null;
try {
oos = new ObjectOutputStream(new FileOutputStream(args[0]));
ois = new ObjectInputStream(new FileInputStream(args[0]));
oos.writeObject(HORIZONTAL);
Orientation obj = (Orientation) ois.readObject();
System.out.println(obj == HORIZONTAL);
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
} finally {
try {
if (oos != null) {
oos.close();
}
if (ois != null) {
ois.close();
}
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
代码1-4运行结果:
root@lejian:/home/software/.io# java Orientation object false
运行代码1-4可以发现,反序列化后的Orientation对象与HORIZONTAL并不是同一个对象,虽然Orientation的构造器是private的,但反序列化依旧可以创建Orientation对象,在这种情况下,可以添加一个readResolve()方法来解决问题,readResolve()方法的返回值会替代原来反序列化的对象,如代码1-5
代码1-5
private Object readResolve() throws ObjectStreamException {
if(value == 1){
return HORIZONTAL;
}
if(value == 2){
return VERTICAL;
}
return null;
}
与writeReplace()方法类似,readResolve()方法可以使用任意的访问控制符,因此父类的readResolve()可能被子类重写,利用readResolve()方法会有一个缺点,就是如果父类已经实现了一个public或者protected的readResolve()方法,而子类没有重写,将会在反序列化时得到父类对象,这显然不是程序想要的结果,也很难排查这样的问题,总是让子类重写readResolve()方法也是一种负担,通常建议是对于final类重写readResolve()方法不会有任何问题,或者重写readResolve()方法应该尽量使用private修饰
原文:http://www.cnblogs.com/baoliyan/p/6236180.html