文章目录
- 1、方式一(实现Serializable接口,通过序列化流)
- 2、方式二 (实现Externalizable接口,重写writeExternal和readExternal方法)
对象序列化的目标是将对象保存到磁盘中,或允许在网络中直接传输对象。对象序列化机制允许把内存中的Java对象转换成平台无关的二进制流,从而允许把这种二进制流持久地保存在磁盘上,或通过网络将这种二进制流传输到另一个网络节点。其他程序一旦获得了这种二进制流,都可以将这种二进制流恢复成原来的Java对象。在Java中,对象的序列化与反序列化被广泛应用到RMI(远程方法调用)及网络传输中
1、方式一(实现Serializable接口,通过序列化流)
实现Serializable接口,通过ObjectOutputStream和ObjectInputStream将对象序列化和反序列化。
Serializable接口是标记接口,是个空接口,用于标识该类可以被序列化
public class ObjectSerializationDemo {
public static void main(String[] args) throws IOException, ClassNotFoundException {
File file = new File("D:\\person.out");
if(!file.exists()){
file.mkdirs();
}
//序列化
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(file));
Person person = new Person("测试",25,Gender.FEMALE,"ennenen");
oos.writeObject(person);
oos.close();
//反序列化
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(file));
Person person1 = (Person) ois.readObject();
System.out.println(person1);
}
}
/**
* Serializable 表示该类可以被序列化
*/
public class Person implements Serializable {
private String name ;
private Integer age;
private Gender gender;
/**
* transient 的作用是防止变量被序列化,在反序列化的时候transient 变量的值被赋值为初始值
* transient 只能修饰变量,不能修饰其他的成分
*/
private transient String introduce;
public Person() {
System.out.println("none-arg constructor");
}
public Person(String name, Integer age, Gender gender,String introduce) {
System.out.println("arg constructor");
this.name = name;
this.age = age;
this.gender = gender;
this.introduce = introduce;
}
//省略get 和 set 方法
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
", gender=" + gender +
", introduce='" + introduce + '\'' +
'}';
}
}
/**
*Enum类实现了Serializable接口,所以枚举类型对象都是默认可以被序列化的。
*/
public enum Gender {
MALE, FEMALE
}
进一步分析:
当重新读取被保存的Person对象时,并没有调用Person的任何构造器,看起来就像是直接使用字节将Person对象还原出来的。 当Person对象被保存到person.out文件后,可以在其它地方去读取该文件以还原对象,但必须确保该读取程序的 CLASSPATH 中包含有 Person.class,哪怕在读取Person对象时并没有显示地使用Person类,如上例所示,否则会抛出 ClassNotFoundException。
简单的来说,Java 对象序列化就是把对象写入到输出流中,用来存储或传输;反序列化就是从输入流中读取对象。
序列化一个对象首先要创造某些OutputStream对象(如FileOutputStream、ByteArrayOutputStream等),然后将其封装在一个ObjectOutputStream对象中,在调用writeObject()方法即可序列化一个对象反序列化的过程需要创造InputStream对象(如FileInputstream、ByteArrayInputStream等),然后将其封装在ObjectInputStream中,在调用readObject()即可
注意对象的序列化是基于字节的,不能使用基于字符的流。
对象引用的序列化
当程序序列化一个Teacher对象时,如果该Teacher对象持有一个Person对象的引用,为了在反序列化时可以正常恢复该Teacher对象,程序会顺带将该Person对象也进行序列化,所以Person类也必须是可序列化的,否则Teacher类将不可序列化。
当使用Java序列化机制序列化可变对象时一定要注意只有第一次调用writeObject()方法来输出对象时才会将对象转换成字节序列,并写入到ObjectOutputStream;在后面程序中即使该对象的Field发生了改变,再次调用writeObject()方法输出该对象时,改变后的Field也不会被输出。
Transient 关键字
Transient 关键字的作用是控制变量的序列化,在变量声明前加上该关键字,可以阻止该变量被序列化到文件中,在被反序列化后,transient 变量的值被设为初始值,如 int 型的是 0,对象型的是 null。Transient 关键字只能用于修饰Field,不可修饰Java程序中的其他成分。
2、方式二 (实现Externalizable接口,重写writeExternal和readExternal方法)
Externalizable接口继承了Serializable接口,替我们封装了两个方法,一个用于序列化,一个用于反序列化。这种方式是将属性序列化,注意这种方式transient修饰词将失去作用,也就是说被transient修饰的属性,只要你在writeExternal方法中序列化了该属性,照样也会得到序列化。
public class ExternalizableDemo {
public static void main(String[] args) throws IOException, ClassNotFoundException {
Person1 person1 = new Person1("测试", 25, Gender.FEMALE, "ennenen");
//序列化
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream);
objectOutputStream.writeObject(person1);
//反序列化
ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(byteArrayOutputStream.toByteArray());
ObjectInputStream objectInputStream = new ObjectInputStream(byteArrayInputStream);
System.out.println(objectInputStream.readObject());
}
}
public class Person1 implements Externalizable {
private String name ;
private Integer age;
private Gender gender;
/**
* transient 的作用是防止变量被序列化,在反序列化的时候transient 变量的值被赋值为初始值
* transient 只能修饰变量,不能修饰其他的成分
* 当类实现Externalizable 接口实现序列化 时,transient 将失去作用
*/
private transient String introduce;
public Person1() {
System.out.println("none-arg constructor");
}
public Person1(String name, Integer age, Gender gender,String introduce) {
System.out.println("arg constructor");
this.name = name;
this.age = age;
this.gender = gender;
this.introduce = introduce;
}
@Override
public void writeExternal(ObjectOutput out) throws IOException {
out.writeInt(age);
}
@Override
public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
// 这里需要用变量接收一下
age = in.readInt();
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
", gender=" + gender +
", introduce='" + introduce + '\'' +
'}';
}
}
/**
*Enum类实现了Serializable接口,所以枚举类型对象都是默认可以被序列化的。
*/
public enum Gender {
MALE, FEMALE
}
使用 Externalizable 接口进行序列化时,读取对象会调用被序列化类的无参构造器去创建一个新的对象,然后再将被保存对象的字段的值分别填充到新对象中,这就是为什么在此次序列化过程中Person类的无参构造器会被调用。
由于这个原因,实现 Externalizable 接口的类必须要提供一个无参构造器,且它的访问权限为public