对象序列化
当我们有时需要将Java中的对象进行传输时,需要将它转换成二进制流保存在文件中,然后其他程序通过这个二进制流再将其恢复成原有的Java类型数据
序列化分为两种,第一种就是序列化,即将Java对象转换成字节序列保存起来
第二种就是反序列化,即将处理后的字节序列恢复成Java对象
实现序列化两种方式:
方式一:实现Serializable接口
当我们要将我们的类对象序列化的时候就要使这个类实现Serializable接口,这个接口只是作为一种标记,标记这个类是否可以被序列化,不需要实现任何抽象方法,而另外一种方式就要去实现内部的抽象方法
序列化的流程:
1、创建一个对象输出流
2、调用该对象的writeObject方法,向指定流输出序列化对象
示例:将Teacher对象序列化存入Java.txt文件中
try( ObjectOutputStream oos=new ObjectOutputStream(new FileOutputStream("Java.txt")); ) { oos.writeObject(new Teacher("张三")); } class Teacher implements Serializable{ String name; public Teacher(){} public Teacher(String name){ this.name=name; } }
反序列化:
1、创建一个对象输入流
2、调用该对象的readObject方法,向指定流输入序列化对象
示例:将序列化后的Teacher读取出来
try( ObjectInputStream ois=new ObjectInputStream(new FileInputStream("Java.txt")); ) { Teacher t=(Teacher)ois.readObject(); } class Teacher implements Serializable{ String name; public Teacher(){} public Teacher(String name){ this.name=name; } }
注意:反序列化得到的类对象源码中必须有相应的类对象,否则会反序列化失败
将序列化得到的Object对象强转成Teacher对象
反序列化是无需构造器来初始化恢复对象的
读取顺序要按照序列化顺序来读取
对象引用的序列化
由上文可以一个类要序列化必需要实现接口,那么当某个类中存在内部类,且该内部类未实现接口,那么整个大类也是无法序列化的。
例如Teacher类实现了接口,而Teacher类中的Student类没有实现接口,那么Teacher是无法序列化的,但是当我们用关键字transient修饰时,Teacher类序列化时就会忽略Student类,进而可以序列化了。
这里有个特殊情形:当重复序列化同一个对象,他会重复的写入文件吗?
答案是不会的,假设如果会重复写入的话,读取的时候就会读取多个对象,就会导致不一致的现象
Java序列化机制采用一种特殊算法,序列化一个对象时会得到一种编号,那么再次序列化这个对象时Java就会检测这个编号是否存在,如果存在只是输出这个序列化编号,而不会再次序列化
举个例子:
Student s=new Student(); Teacher t1=new Teacher("张三",s); Teacher t2=new Teacher("李四",s);
由上述代码可以两个Teacher对象都引用了s对象,那么当序列化s后,再序列化两个Teacher对象时,引用的s对象,就不会再次序列化,只是会输出相应的序列化编号。
注意:
当我们将s对象序列化后,再在程序中修改s对象中的值是不会修改序列化中的内容的,反序列化时得到的也是修改之前的
示例:
try( ObjectOutputStream oos=new ObjectOutputStream(new FileOutputStream("Java.txt")); ObjectInputStream ois=new ObjectInputStream(new FileInputStream("Java.txt")); ) { Teacher t=new Teacher("张三",18); oos.writeObject(t); t.name="李四"; Teacher t2=(Teacher)oos.readObject(); System.out.println(t2.toString()); }
上述代码将t序列化存入文件后,再去修改t的姓名,再反序列化时,得到的依旧是修改之前的对象
自定义序列化
当我们有些时候,我们类中的数据并不希望全部序列化,有些数据保密,这就需要将这些数据编程不可序列化
这是我们就可以将该数据用transient关键字修饰,这时就不会因为不可序列化而报错,只是序列化时会忽略它
示例:
class Teacher implements Serializable{ String name; transient int id; }
这是当我们再去序列化Teacher类时只会序列化name,而id就不会写入,当我们再读取这个对象时,得到的id就为0,因为是默认值
有时我们可以自定义方法去满足我们自定义序列化要求
示例:
class Teacher implements Serialiable{ String name; int id; private void writeObject(ObjectOutputStream out) throws IOException{ out.writeObject(new StringBuffer(name)); out.writeInt(id); } private void readObject(ObjectInputStream in) throws IOException{ this.name=(StringBuffer)in.readObject(); this.id=in.readInt(); } }
序列化机制还有一个代替方法,就是序列化对象转换成其他对象
class Teacher implements Serialiable{ String name; int id; private Object writeReplace() throws IOException{ ArrayList<Object> list=new ArrayList<>(); list.add(name); list.add(id); return list; } }
这是当我们再去序列化Teacher对象时,我们会将Teacher里面的数据已ArrayList形式进行序列化,再进行反序列化时得到的也是一个ArrayList形式的集合
由上可知,Java在序列化某个对象时,会调用该对象的writeReplace方法和writeObject方法
方式二:实现Externalizable接口
注意该接口与上面的接口不同,实现该接口时要实现两个抽象方法
- void readExternal(ObjectInput in):
- void writeExternal(ObjectOutput out):
示例:
class Teacher implements Externalizable{ String name; int id; public Teacher(){} public Teacher(String name,int id){ this.name=name; this.id=id; } public void writeExternal(ObjectOutput out) throws IOException{ out.writeObject(new StringBuffer(name)); out.writeInt(id); } public void readExternal(ObjectInput in) throw IOException{ this.name=((StringBuffer)in.readObject()).toString; this.id=in.readInt(); } }
注意该方法序列化时必须提供无参构造器,因为反序列化时会调用无参构造器创建一个示例,再去调用readExternal方法进行反序列化。
两种方式对比:
- Serializable会自动存储所有数据,而Externaliable可以程序员决定序列化哪些数据
- Serializable易于实现,只需实现接口,而Externaliable需要重写两个方法
- Serializable相比于Externaliable的性能较差