一、基本概念与 Serializable 接口
对象序列化就是把一个对象变为二进制的数据流的一种方法
通过对象序列化可以方便地实现对象的传输或存储
如果一个类的对象想被序列化,则对象所在的类必须实现 java.io.Serializable
接口:
public interface Serializable{}
可以看出该接口并没有定义任何的方法,所以此接口是一个标识接口,表示一个类具备了被序列化的能力。
定义可序列化的类:
import java.io.Serializable;
public class Person implements Serializable{//此类的对象可以被序列化
private String name;
private int age;
public Person(String name,int age){//通过构造方法设置属性内容
this.name = name;
this.age = age;
}
public String toString(){
return "姓名:" + this.name + "; 年龄:" + this.age;
}
}
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
此时的 Person 类已经实现了序列化接口,所以此类的对象是可以经过二进制流进行传输的。而如果要完成对象的输入或输出,还必须依靠对象输出流(ObjectOutputStream)和对象输入流(ObjectInputStream)
使用对象输出流输出序列化的步骤有时也称为序列化,而使用对象输入流读入对象的过程有时也称为反序列化
对象序列化和对象反序列化操作时的版本兼容性问题:
二、对象输出流 ObjectOutputStream
一个对象如果要进行输出,则必须使用 ObjectOutputStream 类:
public class ObjectOutputStream extends OutputStream implements
ObjectOutput,ObjectStreamConstants
- 2
ObjectOutputStream
类属于 OutputStream
的子类:
此类的使用形式与PrintStream 非常相似,在实例化时也需要传入一个 OutputStream 的子类对象,然后根据传入的 OutputStream 子类的对象不同,输出的位置也不同:
import java.io.File;
import java.io.FileOutputStream;
import java.io.ObjectOutputStream;
import java.io.OutputStream;
public class Test{
public static void main(String[] args) throws Exception{
File f = new File("D:" + File.separator + "test.txt");
ObjectOutputStream oos = null;
OutputStream out = new FileOutputStream(f);//文件输出流
oos = new ObjectOutputStream(out);//为对象输出流实例化
oos.writeObject(new Person("Java",30));//保存对象到文件
oos.close();
}
}
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
可以看到,保存的内容全是二进制数据,本身不可修改,因为会破坏其保存格式
一个对象被序列化后,只有属性被序列化
因为每个对象都具备相同的方法,但是每个对象的属性不一定相同,也就是说,对象保存的只有属性信息,那么在序列化操作时也同样是这个道理,只有属性被序列化
三、对象输入流 ObjectInputStream
使用 ObjectInputStream 可以直接把被序列化好的对象反序列化:
public class ObjectInputStream extends InputStream implements
ObjectInput,ObjectStreamConstants
- 2
ObjectInputStream 类也是 InputStream 的子类,需要接收 InputStream 类的实例才可以实例化。
从文件中将 Person 对象反序列化(读取):
import java.io.*;
public class Root{
public static void main(String[] args) throws Exception{
File f = new File("D:" + File.separator + "test.txt");
ObjectInputStream ois = null;
InputStream input = new FileInputStream(f);//文件输入流
ois = new ObjectInputStream(input);//为对象输出流实例化
Object obj = ois.readObject();//读取对象
ois.close();//关闭输出
System.out.println(obj);
}
}
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
从结果可以看出,实现了 Serializable 接口类,对象中的所有属性都可被序列化,如果用户想根据自己的需要选择被序列化的属性,则可以使用另外一种序列化接口 Externalizable 接口。
不需要在所有的类中都实现 Serializable 接口
四、Externalizable 接口
Serializable 接口声明的类的对象都将被序列化,如果用户希望自己指定序列化的内容,则可以让一个类实现 Externalizable 接口
Externalizable 接口是 Serializable 接口的子接口,定义了两个方法:
可知两个接口分别继承 DataOutputStream 和 DataInput ,这样在这两个方法中就可以像 DataOutputStream 和 DataInputStream 那样直接输出和读取各种类型的数据。
如果一个类要使用 Externalizable 实现序列化时,在此类中必须存在一个无参构造方法,因为在反序列化时会默认调用无参构造实例化对象,如果没有此无参构造,则运行时会出现异常,这与 Serializable 接口不同
import java.io.Externalizable;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectOutput;
public class Person implements Externalizable {//此类的对象可以被序列化
private String name;
private int age;
public Person(){}//必须定义无参构造
public Person(String name,int age){//通过构造方法设置属性内容
this.name = name;
this.age = age;
}
public String toString(){
return "姓名:" + this.name + "; 年龄:" + this.age;
}
//覆写此方法,根据需要读取内容,反序列化时使用
public void readExternal(ObjectInput in) throws IOException,
ClassNotFoundException{
this.name = (String)in.readObject();//读取姓名属性
this.age = in.readInt();
}
//覆写此方法,根据需要可以保存属性或具体内容,序列化时使用
public void writeExternal(ObjectOutput out) throws IOException{
out.writeObject(this.name);//保存姓名属性
out.writeInt(this.age);//保存姓名属性
}
}
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
import java.io.*;
public class Test{
public static void main(String[] args) throws Exception{
ser();//序列化
dser();//反序列化
}
public static void ser() throws Exception{//序列化操作
File f = new File("D:" + File.separator + "test.txt");
ObjectOutputStream oos = null;
OutputStream out = new FileOutputStream(f);//文件输出流
oos = new ObjectOutputStream(out);//为对象输出流实例化
oos.writeObject(new Person("Java",30));//保存对象到文件
oos.close();
}
public static void dser() throws Exception{//反序列化操作
File f = new File("D:" + File.separator + "test.txt");
ObjectInputStream ois = null;
InputStream input = new FileInputStream(f);//文件输出流
ois = new ObjectInputStream(input);//为对象输出流实例化
Object obj = ois.readObject();//读取对象
ois.close();//关闭输出
System.out.println(obj);
}
}
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
五、transient 关键字
Serializable 接口实现的操作实际上是将一个对象中的全部属性进行序列化,当然也可以使用 Externalizable 接口实现部分属性的序列化,但这样的操作比较麻烦。
当使用 Serializable 接口实现序列化操作时,如果一个对象中的某个属性不希望被序列化,则可以使用 transient 关键字进行声明:
import java.io.*;
public class Person implements Serializable {//此类的对象可以被序列化
private transient String name;//此属性将不被序列化
private int age;//此属性将被序列化
public Person(String name,int age){//通过构造方法设置属性内容
this.name = name;
this.age = age;
}
public String toString(){
return "姓名:" + this.name + "; 年龄:" + this.age;
}
}
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
六、序列化一组对象
对象输出时只提供了一个对象的输出操作(writeObject(Object obj)),并没有提供多个对象的输出,所以如果现在要同时序列化多个对象,就可以使用对象数组进行操作,因为数组属于引用数据类型,所以可以直接使用 Object 类型进行接收。
序列化一组对象:
import java.io.*;
public class Test{
public static void main(String[] args) throws Exception{
Person per[] = {new Person("Java",30),new Person("Python",31)}//定义对象数组
ser(per);//序列化对象数组
Object o[] = dser();//读取被序列化的对象数组
for (int i=0;i<o.length;i++){
Person p = (Person)o[i];
System.out.println(p);
}
}
public static void ser(Object obj[]) throws Exception{//序列化操作
File f = new File("D:" + File.separator + "test.txt");
ObjectOutputStream oos = null;
OutputStream out = new FileOutputStream(f);//文件输出流
oos = new ObjectOutputStream(out);//为对象输出流实例化
oos.writeObject(obj);//保存对象到文件
oos.close();
}
public static Object[] dser() throws Exception{//反序列化操作
File f = new File("D:" + File.separator + "test.txt");
ObjectInputStream ois = null;
InputStream input = new FileInputStream(f);//文件输出流
ois = new ObjectInputStream(input);//为对象输出流实例化
Object obj[] = (Object[]) ois.readObject();//读取对象数组
ois.close();//关闭输出
return obj;
}
}
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
这里使用对象数组可以保存多个对象,但是数组本身存在长度的限制,为了解决数组中的长度问题,所以使用动态对象数组(类集)完成