Java-对象序列化
对象序列化:将对象保存到磁盘中,或者在网络中传输对象。
这种机制使用一个字节序列表示一个对象,该字节序列包含:对象的类型、属性和数据等信息,字节序列写到文件之后,相当于文件中持久保存了一个对象的信息。与之相反的操作是:将字节序列从文件中读取出来,重构对象,这称为反序列化。
2种特殊操作流:
-
对象序列化流:ObjectOutputStream
继承关系:
java.lang.Object
java.io.OutputStream
java.io.ObjectOutputStreamObjectOutputStream将Java对象的原始数据类型和图形写入OutputStream。可以使用ObjectInputStream读取(重构)对象。可以通过使用流的文件来实现对象的持久存储。如果流是网络套接字流,则可以在另一个主机上或另一个进程中重构对象。
只有支持java.io.Serializable接口的对象才能写入流中。 每个可序列化对象的类被编码,包括类的类名和签名,对象的字段和数组的值以及从初始对象引用的任何其他对象的关闭。
构造方法:
ObjectOutputStream(OutputStream out)
:创建一个写入指定的OutputStream的ObjectOutputStream。将对象序列化的方法:
void writeObject(Object obj):
将指定的对象写入ObjectOutputStream。
要说明的是:
- 如果需要对某个对象进行序列化,这个对象的类必须实现Serializable接口
- Serializable没有任何属性和方法,只是用于标记实现此接口的类可以被序列化。
范例:
Student类:
import java.io.Serializable;
public class Student implements Serializable {
private String name;
private int age;
public Student(){}
public Student(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
测试类:
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;
/*
* 对象序列化:
* 将对象序列化的方法:
* void writeObject(Object obj):`将指定的对象写入ObjectOutputStream。
*
* public interface Serializable:类的序列化由实现java.io.Serializable接口的类启用。不实现此接口的类将不会使任何状态序列化或反序列化。 可序列化类的所有子类型都是可序列化的。 序列化接口没有方法或字段,仅用于标识可串行化的语义。
* */
public class ObjectOutputStreamMain {
public static void main(String[] args) throws IOException {
//将序列化流写入文件,ObjectOutputStream和FileOutputStream构造方法都可能抛出异常,这里先不处理,直接在main()中抛出。
ObjectOutputStream oos = new ObjectOutputStream(
new FileOutputStream("D:\\Java\\Temp\\ObjectSerialization.txt"));
Student stu01=new Student("猪小明",25);
oos.writeObject(stu01);
oos.close();
}
}
编译运行,查看文件内容:
ͭ sr %com.day19.ObjectSerialization.Student��e–̂ I ageL namet Ljava/lang/String;xp t 猪小明
在UTF-8编码下显示的是上面的内容。
-
对象反序列化流:ObjectInputStream
继承关系:
java.lang.Object
java.io.InputStream
java.io.ObjectInputStreamObjectInputStream反序列化之前使用ObjectOutputStream编写的原始数据和对象。
ObjectOutputStream和ObjectInputStream可以分别为与FileOutputStream和FileInputStream一起使用的对象图提供持久性存储的应用程序。 ObjectInputStream用于恢复先前序列化的对象。 其他用途包括使用套接字流在主机之间传递对象,或者在远程通信系统中进行封送和解组参数和参数。
构造方法:
ObjectInputStream(InputStream in)
:创建从指定的InputStream读取的ObjectInputStream。反序列化对象的方法:
Object readObject()
:从ObjectInputStream读取一个对象。范例:
测试类:
import java.io.FileInputStream; import java.io.IOException; import java.io.ObjectInputStream; public class ObjectInputStreamMain { public static void main(String[] args) throws IOException, ClassNotFoundException { ObjectInputStream ois = new ObjectInputStream( new FileInputStream("D:\\Java\\Temp\\ObjectSerialization.txt") ); Object obj = ois.readObject(); Student s = (Student) obj; System.out.println(s.getName()+","+s.getAge()); ois.close(); } }
编译运行,控制台输出:
猪小明,25
在对象序列化和反序列的过程中,可能会抛出这样一个异常:
public class InvalidClassException extends ObjectStreamException
:当序列化运行时检测到类中的以下问题之一时抛出:
- 类的串行版本与从流中读取的类描述符的类型不匹配
- 该类包含未知的数据类型
- 该类没有可访问的无参数构造函数
第二个问题和第三个问题很好解决,当出现第一个问题,很可能是在对象序列化之后,修改了对象的类的源文件,然后反序列化时抛出这个
InvalidClassException
异常:解决这个问题的办法是:在要进行序列化的对象的类中添加一个属性:
private static final long serialVersionUID=XXXL;
这个属性的值是可以自己设置的,这样做的原因是:在Serializable接口中,jdk文档有这样一段描述:
序列化运行时与每个可序列化的类关联一个版本号,称为serialVersionUID,它在反序列化过程中使用,以验证序列化对象的发送者和接收者是否加载了与序列化兼容的对象的类。 如果接收者已经为具有与对应发件人类别不同的serialVersionUID的对象加载了一个类,则反序列化将导致一个InvalidClassException 。 一个可序列化的类可以通过声明一个名为"serialVersionUID"的字段来显式地声明它自己的serialVersionUID,该字段必须是static,final和long类型:
ANY-ACCESS-MODIFIER static final long serialVersionUID = 42L; 如果可序列化类没有显式声明serialVersionUID,则序列化运行时将根据Java(TM)对象序列化规范中所述的类的各个方面计算该类的默认serialVersionUID值。 然而, 强烈建议所有可序列化的类显式声明serialVersionUID值,因为默认的serialVersionUID计算对类细节非常敏感,这些细节可能因编译器实现而异,因此可能会在反序列化期间导致意外的InvalidClassException 。 因此,为了保证不同Java编译器实现之间的一致的serialVersionUID值,一个可序列化的类必须声明一个显式的serialVersionUID值。 还强烈建议,显式的serialVersionUID声明在可能的情况下使用private修饰符,因为这种声明仅适用于立即声明的类 - serialVersionUID字段作为继承成员无效。
显示声明这个值后,即使更改对象所属类,反序列化时也不会抛出这个异常,例如在序列化一个Student对象后,在Student类中添加toString()方法,然后进行反序列化,不会抛出异常,至于反序列化后这个对象调用toString()方法则不会有任何返回值和输出,因为序列化之前这个对象的类中没有toString()方法,反序列化读出来只是一个空的方法体。
如果不想对象所属类的某个属性被序列化,如何做?
使用
transient
关键字修饰属性,可以使这个属性不被序列化,例如,在Student类中用transient
修饰age这个属性,那么在对象序列化时age属性不参与序列化,在反序列化后这个属性则是一个默认值,比如int类型为0,String类型为null。使用
transient
修饰age,然后依次执行对象序列化和反序列化的方法,查看反序列化方法中输出的对象信息:猪小明,0