java-序列化中的自定义方法

java序列化中的自定义方法

在Java中通过默认序列化写和读的方法是有比较大的风险的,Java允许我们通过编写writeObject、readObject、writeReplace、readresolve、readObjectNoData等方法,来实现Java的自定义序列化。在java进行序列化和反序列化的时候,它会通过反射调用被序列化类中的上述方法。

默认的序列化和反序列化

下面的例子,是默认的序列化和反序列化方法。

package serializable;

import java.io.Serializable;
import java.util.Objects;

public class Person implements Serializable{  
	 
	private static final long serialVersionUID = 2883313162693024310L;
	
	public Person(String name, Integer age) {
		if (Objects.isNull(age) || Objects.isNull(name)) {
			throw new RuntimeException("年龄或姓名不能为空");
		} else if (age < 18) {
			throw new RuntimeException("年龄不能小于18岁");
		}
		this.name = name;
		this.age = age;
	}

	private String name;
	
	private Integer age;
	

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

	public Integer getAge() {
		return age;
	}

	public void setAge(Integer age) {
		this.age = age;
	}

	@Override
	public String toString() {
		return "Person [name=" + name + ", age=" + age + "]";
	}

} 
package serializable;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.util.HashSet;
import java.util.Set;


public class SimpleSerial {  
	 
    public static void main(String[] args) throws Exception {  
        File file = new File("D:\\new\\person.txt");  
        ObjectOutputStream oout = new ObjectOutputStream(new FileOutputStream(file));  
        Person person = new Person("张三", 18); 
        oout.writeObject(person);  
        oout.close();
 
        ObjectInputStream oin = new ObjectInputStream(new FileInputStream(file));  
        Object newPerson = oin.readObject(); 
        oin.close();  
        System.out.println(newPerson);  
    }  
} 

writeObject

在对象中,如果要通过writeObject自定义序列化内容,需要把被序列化的字段定义为transient, 它可以保证被修饰的字段不被序列化,如下列代码

    // 这时候age不会被序列化到文档中,读出的age也默认为null
	private transient Integer age;

通过writeObject,可以自定义哪些字段可以写入序列化中,将name和age都用transient修饰,然后编写下面writeObject方法,只会序列化age。这里要注意defaultWriteObject去掉并不会报错,但是默认添加。

	private transient String name;
	
	private transient Integer age;
	...
	private void writeObject(ObjectOutputStream oos) throws IOException {
		System.out.println("writeObject");
		oos.defaultWriteObject();
		oos.writeInt(age);
	}

readObject

如果被序列化对象中增加了writeObject方法,那么一定要增加readObject方法,并且该方法读取字段的顺序和writeObject中输出字段的顺序一致

	private void readObject(ObjectInputStream ois) throws ClassNotFoundException, IOException {
		System.out.println("readObject");
		ois.defaultReadObject();
		age = ois.readInt();
	}
  • 对于对象引用域必须保持为私有的类,要保护性地拷贝这些域中的每个对象。不可变类的可变组件就属于这一类别。
  • 对于任何约束条件,如果检查失败,则抛出一个InvalidObjectException 异常。这些检查动作应该跟在所有的保护性拷贝之后。

writeReplace

如果一个序列化类中含有Object writeReplace()方法,那么实际序列化的对象将是作为writeReplace方法返回值的对象,而且序列化过程的依据是实际被序列化对象的序列化实现。

	private Object writeReplace() {
		Person p = new Person("李四", 21);
		p.setSex(2);
		return p;
	}

readResolve

readResolve 特性允许你用readObject 创建的实例代替另一个实例.对于一个正在被反序列化的对象,如果它的类定义了一个readResolve 方法,并且具备正确的声明,那么在反序列化之后,新建对象上的readResolve方法就会被调用。然后,该方法返回的对象引用将被返回,取代新建的对象。

  1. 如果依赖readResolve进行实例控制,带有对象引用类型的所有实例域则都必须声明为transient
	private Object readResolve() {
		Person p = new Person("李四", 21);
		p.setSex(2);
		return p;
	}

readObjectNoData

下面是Serializable接口中对该方法的描述:
The readObjectNoData method is responsible for initializing the state of the object for its particular class in the event that the serialization stream does not list the given class as a superclass of the object being deserialized. This may occur in cases where the receiving party uses a different version of the deserialized instance’s class than the sending party, and the receiver’s version extends classes that are not extended by the sender’s version.This may also occur if the serialization stream has been tampered; hence, readObjectNoData is useful for initializing deserialized objects properly despite a “hostile” or incomplete source stream.

(个人翻译和理解)如果序列化流未将要反序列化的给定类对象的超类列出,则ReadObjectNodeata方法负责为其初始化对象的状态。在接收方使用反序列化实例类的版本不同于发送方的情况下,可能会发生这种情况,并且接收方的版本扩展了发送方版本类。如果序列化流已被篡改,也可能发生这种情况;因此,尽管源流“敌对”或不完整,但readObjectNoData对于正确初始化反序列化对象非常有用。

当序列化流不完整时,readObjectNoData()方法可以用来正确的初始化反序列化的对象。例如,接收方使用的反序列化类的版本不同于发送方,或者接收方版本扩展的类不是发送方版本扩展的类,或者序列化流被篡改时,系统都会调用readObjectNoData()方法来初始化反序列化对象。

  • [示例]
    先编写Person不继承Animal,实例后序列化。
    将Person继承Animal,Animal中增加了readObjectNoData方法,进行反序列化,会执行Animal中的readObjectNodata方法
package serializable;

import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.util.Objects;

public class Person implements Serializable{  
	 
	private static final long serialVersionUID = 2883313162693024310L;
	
	public Person(String name, Integer age) {
		if (Objects.isNull(age) || Objects.isNull(name)) {
			throw new RuntimeException("年龄或姓名不能为空");
		} else if (age < 18) {
			throw new RuntimeException("年龄不能小于18岁");
		}
		this.name = name;
		this.age = age;
	}

	private String name;
	
	private Integer age;

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

	public Integer getAge() {
		return age;
	}

	public void setAge(Integer age) {
		this.age = age;
	}

	@Override
	public String toString() {
		return "Person [name=" + name + ", age=" + age + "]";
	}
}
package serializable;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.util.HashSet;
import java.util.Set;


public class SimpleSerial {  
	 
    public static void main(String[] args) throws Exception {  
        File file = new File("D:\\new\\person.txt");  
        ObjectOutputStream oout = new ObjectOutputStream(new FileOutputStream(file));  
        Person person = new Person("张三", 18); 
        oout.writeObject(person);  
        oout.close();
 
    }  
} 

增加Animal

package serializable;

import java.io.Serializable;

public class Animal implements Serializable {

	private static final long serialVersionUID = 4099582477634514561L;
	
	private Integer tall;

	public Integer getTall() {
		return tall;
	}

	public void setTall(Integer tall) {
		this.tall = tall;
	}

	private void readObjectNoData() {
		System.out.println("Animal-readObjectNoData");
	}

	@Override
	public String toString() {
		return "Animal [tall=" + tall + "]";
	}

}

public class Person extends Animal implements Serializable{  
package serializable;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.util.HashSet;
import java.util.Set;


public class SimpleSerial {  
	 
    public static void main(String[] args) throws Exception {  
        File file = new File("D:\\new\\person.txt");  
 
        ObjectInputStream oin = new ObjectInputStream(new FileInputStream(file));  
        Object newPerson = oin.readObject(); 
        oin.close();  
        System.out.println(newPerson);  
    }  
    
} 


// Animal-readObjectNoData
// Person [name=张三, age=18]

你可能感兴趣的:(java基础知识)