本文是基于Linux环境运行,读者阅读前需要具备一定Linux知识
自定义序列化
在一些情况下,如果某个类的一些属性不希望被序列化,或者没有实现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 void writeObject(java.io.ObjectOutputStream out) throws IOException
- private void readObject(java.io.ObjectInputStream in) throws ClassNotFoundException, IOException
以上方法的访问权限必须为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修饰