ObjectOutputStream:序列化
- ObjectOutputStream:序列化,将对象作为流输出到文件中存储起来
- 报异常:java.io.NotSerializableException:
Student对象不支持序列化,没有实现Serializable接口 - 参与序列化和反序列化的对象,必须实现Serializable接口
- 注意:通过源代码发现:Serializable接口只是一个标志接口:
public interface Serializable{}
这个接口中什么代码都没有
那么它起到一个什么作用呢?
起到标识的作用,标志的作用,java虚拟机看到这个类实现了这个接口,
会为该类自动生成一个序列化版本号 - 创建一个Student类用于序列化对象:
public class Student implements Serializable {
//java虚拟机看到Serializable接口后,会自动生成一个序列化版本号
//这里没有手动写出来,java虚拟机会默认提供这个序列化版本号
String name;
int age;
public Student() {
}
public Student(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
- ObjectOutputStream对象输出流,进行序列化
public class ObjectOutputStreamTest01 {
public static void main(String[] args) throws Exception {
//序列化
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("src\\io\\objectoutputstream\\students"));
//序列化对象
oos.writeObject(new Student("zhangsan",18));
//flush close
oos.flush();
oos.close();
}
}
ObjectInputStream:反序列化
public class ObjectInputStreamTest01 {
public static void main(String[] args) throws Exception{
//反序列化
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("src\\io\\objectoutputstream\\students"));
//反序列化对象,返回一个学生对象,所以会调用学生对象的toString方法
System.out.println(ois.readObject());
//close
ois.close();
}
}
//运行结果:
Student{name='zhangsan', age=18}
同时对多个对象进行序列化和反序列化
- transient关键字:游离的,被该关键字修饰的变量不参与序列化
- 经transient修饰的变量在序列化时保存的是类型默认值
- 同时对多个对象进行序列化需要将对象存进集合中,对集合进行序列化
- 创建新的User类集合:
/*
建议将序列化版本号手动写出来,不建议自动生成
*/
public class User implements Serializable {
private static final long serialVersionUID = -1592057586725198947L;
//transient:游离的,不参与序列化
String name;
transient int age; //age不参与序列化
int id;
public User(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
", age='" + age + '\'' +
'}';
}
}
- 对多个对象进行序列化
public class ObjectOutputStreamTest02 {
public static void main(String[] args) throws Exception{
//序列化
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("src\\io\\objectoutputstream\\Users"));
//创建集合
List<User> userList = new ArrayList<>();
//向集合中添加对象
userList.add(new User("张三",18));
userList.add(new User("李四",20));
//序列化集合对象
oos.writeObject(userList);
//flush close
oos.flush();
oos.close();
}
}
- 对多个对象进行反序列化
public class ObjectInputStreamTest02 {
public static void main(String[] args) throws Exception{
//反序列化
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("src\\io\\objectoutputstream\\Users"));
//创建集合
List<User> list;
//反序列化对象
list = (List<User>) ois.readObject();
//遍历
for (User user : list){
System.out.println(user);
}
//close
ois.close();
}
}
//运行结果:
User{name='张三', age='0'}
User{name='李四', age='0'}
结论:
- 修改对象类文件后,如果不重新序列化,会出InvalidClassException异常
• Exception in thread “main” java.io.InvalidClassException:
io.objectoutputstream.User; local class incompatible:
stream classdesc serialVersionUID = -3965806672486621565, //修改后
local class serialVersionUID = -1592057586725198947 //修改前
- 为了避免上述异常的发生,建议手动添加序列化版本号
- java语言中是采用什么机制来区分类的
第一:首先通过类名来进行对比,如果类名不一样,肯定不是同一个类
第二:如果类名一样,再怎么进行类的区别?靠序列化版本号进行区分
不同的人编写了同一个类,但“这两个类确实不是同一个类”,这时序列化版本就起上作用了
对于java虚拟机来说,java虚拟机是可以区分开这两个类的,因为这两个类都实现了Serializable接口
都有默认的序列化版本号,他们的序列化版本号不一样。所以区分开了
这种自动生成序列化版本号有什么缺陷:
一旦代码确定之后,不能进行后续的修改,因为只要修改,必然会重新编译,
此时会生成全新的序列化版本号,这个时候java虚拟机会认为这是一个全新的类 - 最终结论:
凡是一个类实现了Serializable接口,建议给该类提供一个固定不变的序列化版本号
这样,以后这个类即使代码修改了,但是版本号不变,java虚拟机会认为是同一个类 - 使用IDEA工具添加序列化版本号的方法: