序列化和反序列化
序列化(Serialize):java对象存储到文件中。将java对象的状态保存下来的过程。
反序列化(DeSerialize):将硬盘上的数据重新恢复到内存当中,恢复成java对象。
-
java.io.NotSerializableException 异常
Student对象不支持序列化
-
参与序列化和反序列化的对象,必须实现Serializable接口
public class Student implements Serializable { //java虚拟机看到这个接口之后,会为该类自动生成一个版本化序列号。 //这里没有手动写出来,java虚拟机会默认提供这个序列化版本号。 private int no; private String name; public Student() { } public Student(int no, String name) { this.no = no; this.name = name; } public int getNo() { return no; } public void setNo(int no) { this.no = no; } public String getName() { return name; } public void setName(String name) { this.name = name; } @Override public String toString() { return "Student{" + "no=" + no + ", name='" + name + '\'' + '}'; } }
-
通过源代码发现,Serializable接口只是一个标志接口:
public interface Serializable { }
这个接口当中什么代码都没有。只起到标识的作用,java虚拟机看到这个类实现了这个接口,可能会对这个类进行特殊待遇。
Seralizable这个标志接口是给java虚拟机参考的,java虚拟机看到这个接口之后,会为该类自动生成一个序列化版本号。
这里没有手动写出来,java虚拟机会默认提供这个序列化版本号。
public class ObjectOutputStreamTest01 { public static void main(String[] args) { Student zhangsan = new Student(5, "zhangsan"); ObjectOutputStream oos = null; try { oos = new ObjectOutputStream(new FileOutputStream("student")); oos.writeObject(zhangsan); oos.flush(); } catch (IOException e) { e.printStackTrace(); }finally { if ( oos != null) { try { oos.close(); } catch (IOException e) { e.printStackTrace(); } } } } }
public class ObjectInputStreamTest01 { public static void main(String[] args) { ObjectInputStream ois = null; try { ois = new ObjectInputStream(new FileInputStream("student")); //反序列化回来是一个学生对象,所以会调用学生的toString()方法 System.out.println(ois.readObject()); } catch (IOException e) { e.printStackTrace(); } catch (ClassNotFoundException e) { e.printStackTrace(); } finally { if (ois != null) { try { ois.close(); } catch (IOException e) { e.printStackTrace(); } } } } }
-
序列化版本号有什么用
如果某一个项目再十年前序列化了一个学生类,过了很久,Student这个类源代码改动了。
源代码改动之后,需要重新编译,编译之后生成了全新的字节码文件。
并且class文件再次运行的时候,java虚拟机生成的序列化版本号也会发生相应的改变。
java.io.InvalidClassException:
com.bean.Student;
local class incompatible:
stream classdesc serialVersionUID = 5121880095660446367(十年前),
local class serialVersionUID = -5381600005822386241(十年后)
java语言中是通过什么机制来区分类的
- 首先通过类名进行比对,如果类名不一样,肯定不是同一个类
如果类名一样,靠序列化版本号进行区分。
小鹏编写了一个类:com.bean.Student implements Serializable
小李编写了一个类:com.bean.Student implements Serializable
不同的人编写了同一个类,但
这两个类确实不是同一个类
这个时候序列化版本号就有用了优点
:对于java虚拟机来说,java虚拟机是可以区分开这两个类的,因为这两个类都是先了Serializable接口,都有默认的序列化版本号,他们的序列化版本号不一样。所以区分开了。(这是自动生成序列化版本号的好处)缺陷
:一旦代码确定之后,不能进行后续的代码修改,因为只要修改,必然会重新编译,此时会生成全新的序列化版本号,此时java虚拟机会认为这是一个全新的类。结论:凡是一个类实现了Serializable接口,建议给该类提供一个固定不变的序列化版本号。以后这个类即使代码修改了,但是版本号不变,java虚拟机会认为是同一个类
建议将序列化版本号手动的写出来,不建议自动生成
public class Student implements Serializable{ //java虚拟机看到这个接口之后,会为该类自动生成一个版本化序列号。 //这里没有手动写出来,java虚拟机会默认提供这个序列化版本号。 //建议将序列化版本号手动写出来。不建议自动生成 private static final long serialVersionUID = 111L; private int no; private String name; //过了很久,Student这个类源代码改动了。 // //源代码改动之后,需要重新编译,编译之后生成了全新的字节码文件。 // //并且class文件再次运行的时候,java虚拟机生成的序列化版本号也会发生相应的改变。 private int age; private String email; public Student() { } public Student(int no, String name) { this.no = no; this.name = name; } public int getNo() { return no; } public void setNo(int no) { this.no = no; } public String getName() { return name; } public void setName(String name) { this.name = name; } @Override public String toString() { return "Student{" + "no=" + no + ", name='" + name + '\'' + '}'; } }
-
IDEA工具自动生成序列化版本号
Settings --> Editor --> Inspections --> 搜索Serializable
最后在类名那里使用快捷键alt + Enter就可以自动生成序列化版本号
-
序列化与反序列化多个对象
存储第二个对象就会报错,所以使用集合存储多个对象
//序列化 public class ObjectOutputStreamTest02 { public static void main(String[] args) { List<User> userList = new ArrayList<>(); userList.add(new User(1,"zhangsan")); userList.add(new User(2,"lisi")); userList.add(new User(3,"wangwu")); userList.add(new User(4,"zhaoliu")); ObjectOutputStream oos = null; try { oos = new ObjectOutputStream(new FileOutputStream("users")); //序列化一个集合,这个集合对象中放了很多其他对象。 oos.writeObject(userList); oos.flush(); } catch (IOException e) { e.printStackTrace(); }finally { if (oos != null) { try { oos.close(); } catch (IOException e) { e.printStackTrace(); } } } } }
//反序列化 public class ObjectInputStreamTest02 { public static void main(String[] args) { ObjectInputStream ois = null; try { ois = new ObjectInputStream(new FileInputStream("users")); List<User> userList = (List<User>)ois.readObject(); for (User user : userList) { System.out.println(user); } } catch (IOException e) { e.printStackTrace(); } catch (ClassNotFoundException e) { e.printStackTrace(); } finally { if (ois != null) { try { ois.close(); } catch (IOException e) { e.printStackTrace(); } } } } } /* User{no=1, name='zhangsan'} User{no=2, name='lisi'} User{no=3, name='wangwu'} User{no=4, name='zhaoliu'}*/
-
不希望某个元素序列化
private int no; private transient String name;
//序列化与反序列化后的结果为 /* User{no=1, name='null'} User{no=2, name='null'} User{no=3, name='null'} User{no=4, name='null'} */