Java中的序列化对象是指将内存中的对象转换成字节流的形式,以方便保存(写入文件、数据库)和传输。反序列化对象则是相应的逆过程,将从文件或数据库或网络中获取到的字节流重新构造成序列化前的对象实例并写入到内存中,这使得我们可以在程序运行过程中使用该对象。
如果想要某个类的实例能够序列化成字节流的形式,那么这个类需要实现java.io.Serializable接口(Serializable接口是一个标记接口,没有任何的属性和方法,只是专门用来标记某个类实例化后的对象是可序列化的,java中类似的标记接口还有Cloneable和Remote)。
总之序列化对象的优点主要有如下两点:
(1)持久化对象的状态;
(2)通过网络传输对象;
在序列化对象时,我们需要注意如下几点:
(1)只有非静态的成员才能被序列化,如果某个非静态属性不希望被序列化,我们可以添加关键字transient修饰该属性;
(2)类中依赖的其他类也必须是可序列化的;
(3)如果父类中实现了Serializable接口表明他是可序列化的,那么其子类不用再实现Serializable接口;
(4)反序列化时,类的构造函数不会执行,反序列化并不是重新实例化该类,而是从字节流中恢复对象;
SerialVersionUID:
在对象序列化过程中,往往会关联一个ID来标识序列化的对象,这个ID称为SerialVersionUID。SerialVersionUID的作用在于反序列化对象时,检查当前的JVM环境下是否加载了对应的类,如果SerialVersionUID不匹配,反序列化将会失败,并抛出InvalidClassException异常。这个其实也很好理解,假设我们在A环境下的内存中序列化了某个对象,然后通过网络将字节流传输到B环境,B环境想要通过反序列化恢复这个对象实例,如果B环境下JVM没有加载对应的类,这个对象在B环境下也是无法使用的,而SerialVersionUID在这个过程中就是用于检查的唯一标识。
通过如下命令我们还可以查看某个类的SerialVersionUID(需要切到class文件所在路径):
serialver [-classpath 类路径] [-show] [类名称...]
将序列化对象写入文件:
将对象序列化后产生的字节流写入到文件中需要通过ObjectOutputStream类来完成,objectOutputStream实例上有一个writeObject方法可以将对象写入文件中,假设当前有个可序列化的Person类:
import java.io.Serializable;
public class Person implements Serializable {
private static final long SerialVersionUID = 42L;
private String name;
private int age;
private transient String sex;
public Person(String name, int age, String sex) {
this.name = name;
this.age = age;
this.sex = sex;
}
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 getSex() {
return sex;
}
public void setSex(String sex) {
this.sex = sex;
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
", sex='" + sex + '\'' +
'}';
}
}
将对象写入本地文件serialPerson.ser中:
import java.io.*;
public class Main {
public static void main(String[] args) {
Person serialPerson = new Person("zjh", 25, "male");
String serialFileName = "serialPerson.ser"; // 序列化对象的文件名应该使用ser作为后缀
try {
FileOutputStream fileOutputStream = new FileOutputStream(serialFileName);
ObjectOutputStream objectOutputStream = new ObjectOutputStream(fileOutputStream);
objectOutputStream.writeObject(serialPerson);
objectOutputStream.close();
fileOutputStream.close();
System.out.println("object serialPerson has been serialized");
} catch (IOException e) {
e.printStackTrace();
}
}
}
如果想从ser文件中反序列对象,可以使用ObjectOutputStream类的readObject方法:
import java.io.*;
public class Main {
public static void main(String[] args) {
......
Person deserialPerson = null;
try {
FileInputStream fileInputStream = new FileInputStream(serialFileName);
ObjectInputStream objectInputStream = new ObjectInputStream(fileInputStream);
deserialPerson = (Person) objectInputStream.readObject();
objectInputStream.close();
fileInputStream.close();
System.out.println(deserialPerson);
} catch (ClassNotFoundException | IOException e) {
e.printStackTrace();
}
}
}
最终的打印结果如下:
从中也可以看到,声明为transient的属性反序列化后是null。