Java从零开始学三十九(对象序列化)
一、序列化
将对象的状态存储到特定存储介质中的过程
对象序列化,就是把一个对象变为二进制的数据流的一种方法,通过对象序列化可以方便的实现对象的传输或存储。
序列化保存对象的“全景图”,构建对象的“全景天窗”.
如果一个类的对象想被序列化,则对象所在的类必须实现java.io.Serializable接口
二、对象的序列化和反序列化
要想完成对象的输入或输出,还必须依靠对象输出流(ObjectOutputStream)和对象输入流(ObjectInputStream)
使用对象输出流输出序列化对象的步骤,有时也称为序列化,而使用对象输入流读入对象的过程,有时也称为反序列化
序列化步骤:
- 创建一个对象输出流ObjectOutputStream
- writeObject()方法输出序列化对象
反序列化步骤:
- 创建一个对象输入流ObjectInputStream
- readObject()方法读取流中的对象
三、对象输出流(ObjectOutputStream)和对象输入流(ObjectInputStream)
3.1、对象输出流:ObjectOutputStream
No.
|
方法或常量
|
类型
|
描述
|
1
|
public ObjectOutputStream(OutputStream out) throws IOException
|
构造
|
传入输出的对象
|
2
|
public final void writeObject(Object obj) throws IOException
|
普通
|
输出对象
|
此类的使用形式与PrintStream非常的相似,在实例化时也需要传入一个OutputStream的子类对象,之后根据传入的OutputStream子类的对象不同,输出的位置也不同。
3.2、对象输入流:ObjectInputStream
No.
|
方法或常量
|
类型
|
描述
|
1
|
public ObjectInputStream(InputStream in) throws IOException
|
构造
|
构造输入对象
|
2
|
public final Object readObject() throws IOException, ClassNotFoundException
|
普通
|
从指定位置读取对象
|
此类也是InputStream的子类,与PrintStream类的使用类似,此类同样需要接收InputStream类的实例才可以实例化
四、transient关键字
当使用Serializable接口实现序列化操作时,如果一个对象中的某个属性不希望被序列化的话,则可以使用transient关键字进行声明
import java.io.Serializable; 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() { // 覆盖toString(),输出信息 return "姓名:" + this.name + ";年龄:" + this.age; } }
五、实现反序列化注意事项
- 反序列化过程无需要使用构造器生成对象
- 按顺序反序列化恢复对象
- 父类Serializable或者存在无参数构造方法
六、例子
package com.pb.serializable; import java.io.Serializable; /* * 学生类 并实现接口Serializable接口,使用之可以序列化 */ /* * 序列化 * 1.创建一个对象,这个对象将被序列化,并写入文件中,前提是这个类实现了Serializable接口 * 2.实例化ObjectOutputStream的对象 * 3.调用ObjectOutputStream的writerObject()方法,将对象写入流中 * 4.关闭流 * transient关键字 * 可以保护对象中的敏感信息不被写入到文件中 * 反序列化 * 1.实例化ObjectInputStream对象 * 2.调用ObjectInputStream的readObject()方法,获取对象时,要进行强制类型转换 * 3.关闭流 */ public class Student implements Serializable { private String name; // 姓名 private int age; // 年龄 private String gender; // 性别 private transient String password; // 密码属性不能被序列化 // 构造方法 public Student() { // 无参 } // 有参数 public Student(String name, int age, String gender, String password) { this.name = name; this.age = age; this.gender = gender; this.password = password; } //getter、setter方法 public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { if(age>0&&age<150) this.age = age; } public String getGender() { return gender; } public void setGender(String gender) { if(gender.equals("男") || gender.equals("女")) this.gender = gender; } public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } // 自我介绍 public void say() { System.out.println("姓名:" + this.name + "\t年龄:" + this.age + "\t性别:" + this.gender+"\t密码 : "+this.password); } }
序列化:
package com.pb.serializable; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.ObjectOutputStream; import java.util.ArrayList; import java.util.List; /* * 序列化 * 1.创建一个对象,这个对象将被序列化,并写入文件中,前提是这个类实现了Serializable接口 * 2.实例化ObjectOutputStream的对象 * 3.调用ObjectOutputStream的writerObject()方法,将对象写入流中 * 4.关闭流 * transient关键字 * 可以保护对象中的敏感信息不被写入到文件中 */ public class SerializableObj { public static void main(String[] args) { try { //1.声明一个文件输出流 FileOutputStream fos = new FileOutputStream("d:/test/obj.txt"); //2.声明ObjectOutputStream流对象 ObjectOutputStream oos=new ObjectOutputStream(fos); //也可以2步合一步 //ObjectOutputStream oos=new ObjectOutputStream(new FileOutputStream("d:/test/obj.txt")); Student stu1=new Student("张三", 23, "男", "123456"); Student stu2=new Student("李四", 24, "女", "123123"); Student stu3=new Student("王五", 25, "男", "123321"); Student stu4=new Student("赵六", 26, "男", "999999"); //创建集合并添加 List<Student> list=new ArrayList<Student>(); list.add(stu1); list.add(stu2); list.add(stu3); list.add(stu4); //3.将student对象序列化,写入输出oos流 oos.writeObject(stu1); oos.writeObject(stu2); oos.writeObject(stu3); oos.writeObject(stu4); oos.writeObject(list); //4.关闭流 oos.close(); fos.close(); System.out.println("=======序列化完成======"); } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } } }
反序列化:
package com.pb.serializable; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.ObjectInputStream; import java.util.List; /* * 反序列化 * 1.实例化ObjectInputStream对象 * 2.调用ObjectInputStream的readObject()方法,获取对象时,要进行强制类型转换 * 3.关闭流 */ public class ReadSerializableObj { public static void main(String[] args) { try { //1.声明一个文件输出流 FileInputStream fis = new FileInputStream("d:/test/obj.txt"); //2.实例化ObjectInputStream ObjectInputStream ois=new ObjectInputStream(fis); //也可以全2为一 //ObjectInputStream ois=new ObjectInputStream(new FileInputStream("d:/test/obj.txt")); //3.声明一个Student对象和集合 System.out.println("=====反序列化学生对象======="); Student stu1= (Student) ois.readObject(); stu1.say(); Student stu2= (Student) ois.readObject(); stu2.say(); Student stu3= (Student) ois.readObject(); stu3.say(); Student stu4= (Student) ois.readObject(); stu4.say(); System.out.println("=====反序列化集合对象======="); List<Student> list=(List<Student>) ois.readObject(); for (Student s : list) { s.say(); } //4.关闭流 ois.close(); fis.close(); System.out.println("====反序列完成===="); } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } catch (ClassNotFoundException e) { e.printStackTrace(); } } }
结果:
=====反序列化学生对象======= 姓名:张三 年龄:23 性别:男 密码 : null 姓名:李四 年龄:24 性别:女 密码 : null 姓名:王五 年龄:25 性别:男 密码 : null 姓名:赵六 年龄:26 性别:男 密码 : null =====反序列化集合对象======= 姓名:张三 年龄:23 性别:男 密码 : null 姓名:李四 年龄:24 性别:女 密码 : null 姓名:王五 年龄:25 性别:男 密码 : null 姓名:赵六 年龄:26 性别:男 密码 : null ====反序列完成====
从结果中可以看到:password使用transient关键字后,没有被序列化,反序列化中,读出的是默认字段类型的默认值null
七、包含引用类型属性的对象序列化
引用类必须也为可序列化
public class Teacher implements Serializable { private String name; // 姓名 private int age; // 年龄 private String gender; // 性别 private transient String password; // 密码属性不能被序列化 private Student stu; //这里的Student类必须也是可以序列化的, }
这里有个Teacher类,但属性中有一个Student类的对象,这时Teacher要实现序列时,Student必须也要实现Serializable接口才可以
7.1、序列化算法
- 对象分配序列号
- 当程序试图序列化一个对象时,将会检查是否已经被序列化,只有序列化后的对象才能被转换成字节序列输出
- 如果对象已经被序列化,则程序直接输出一个序列化编号,而不在重新序列化
7.2、序列化机制
Teacher类:
package com.pb.serializable; import java.io.Serializable; /* * 教师类实例Serializable接口 */ public class Teacher implements Serializable { private String name; // 姓名 private int age; // 年龄 private String gender; // 性别 private transient String password; // 密码属性不能被序列化 private Student stu; //这里的Student类必须也是可以序列化的, //构造方法 public Teacher() { //无参数 } public Teacher(String name, int age, String gender, String password, Student stu) { //有参数 this.name = name; this.age = age; this.gender = gender; this.password = password; this.stu = stu; } //getter、setter方法 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; } public String getGender() { return gender; } public void setGender(String gender) { this.gender = gender; } public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } public Student getStu() { return stu; } public void setStu(Student stu) { this.stu = stu; } // 自我介绍 public void say() { System.out.println("姓名:" + this.name + "\t年龄:" + this.age + "\t性别:" + this.gender+"\t密码 : "+this.password); } }
序列化和化序列化类:
package com.pb.serializable; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; /* * 老师类的序列化和反序列化 2个老师引用同一个学生对象 */ public class WriterTeacherObj { public static void main(String[] args) { /* * 序列化 * 1.序列化时时候,会查找当前对象的序列化编号是否存在,不存在,保存该对象 * 2.写入操作时,当对象的序列化编号存在时候,会输出一个当前对象的序列化编号,而不是当前对象 * 3.反序列化对象时, 程序会自动查找,当前对象的序列化编号,之后如果,发现当前的对象的序列化编号被其它对象引用时,则返回同一个对象 */ try { //1.实例化ObjectOutputStream ObjectOutputStream oos=new ObjectOutputStream(new FileOutputStream("d:/test/teacher.txt")); //2.实例化老师对象 Student student=new Student("马达", 23, "男", "123123"); //2个老师引用同一个学生对象 Teacher teacher_han=new Teacher("韩冰", 22, "女", "123456789", student); Teacher teacher_zhang=new Teacher("张三丰", 108, "男", "123456789", student); //3.对象序列 oos.writeObject(teacher_han); oos.writeObject(teacher_zhang); //4.关闭流 oos.close(); System.out.println("====================序列化完成================="); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } /* * 反序列化 */ System.out.println("==================开始反序列化================"); try { //1.实例化ObjectInputStream对象 ObjectInputStream ois=new ObjectInputStream(new FileInputStream("d:/test/teacher.txt")); //2.反序列声明一个Teacher对象强制类型转换 Teacher teacher_han=(Teacher) ois.readObject(); Teacher teacher_zhang=(Teacher) ois.readObject(); //3.输入Teacher中的信息 System.out.println("==================老师信息================"); teacher_han.say(); teacher_zhang.say(); System.out.println("=================老师中学生信息============="); teacher_han.getStu().say(); teacher_zhang.getStu().say(); //4.关闭流 ois.close(); System.out.println("===========反序列化完成========="); //判断2个老师的学生是否为同一个 if(teacher_han.getStu()==teacher_zhang.getStu()){ System.out.println("张老师和韩老师教的是同一个学生"); }else{ System.out.println("张老师和韩老师教的不是同一个学生"); } } catch (IOException e) { e.printStackTrace(); } catch (ClassNotFoundException e) { e.printStackTrace(); } } }
结果:
====================序列化完成================= ==================开始反序列化================ ==================老师信息================ 姓名:韩冰 年龄:22 性别:女 密码 : null 姓名:张三丰 年龄:108 性别:男 密码 : null =================老师中学生信息============= 姓名:马达 年龄:23 性别:男 密码 : null 姓名:马达 年龄:23 性别:男 密码 : null ===========反序列化完成========= 张老师和韩老师教的是同一个学生