对象序列化(二)

本文是基于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修饰

原文地址:https://www.cnblogs.com/baoliyan/p/6236180.html