Java 对象流
什么是Java对象流?
Java对象流是Java IO体系中的一个重要组成部分,它允许我们将Java对象转换为字节序列(称为"序列化"),以及将这些字节序列恢复为原始Java对象(称为"反序列化")。对象流主要通过ObjectOutputStream
和ObjectInputStream
这两个类实现,这两个类分别用于对象的序列化和反序列化。
核心概念
- 序列化(Serialization):将Java对象转换为字节序列的过程
- 反序列化(Deserialization):将字节序列恢复为Java对象的过程
为什么需要对象流?
在实际应用中,对象流主要有以下几个用途:
- 持久化存储:将程序中的对象状态保存到文件中,以便在程序重新启动后恢复。
- 网络传输:在网络上传输Java对象。
- 深拷贝:通过序列化和反序列化可以实现对象的深拷贝 。
- 远程方法调用(RMI):在分布式系统中,使用Java RMI时需要对象的序列化和反序列化。
相关类介绍
ObjectOutputStream
ObjectOutputStream
类用于将Java对象序列化为字节序列。它是OutputStream
的子类,提供了写入对象的能力。
主要方法:
writeObject(Object obj)
:将指定的对象写入ObjectOutputStreamflush()
:刷新流close()
:关闭流
ObjectInputStream
ObjectInputStream
类用于将字节序列反序列化为Java对象。它是InputStream
的子类,提供了读取对象的能力。
主要方法:
readObject()
:从ObjectInputStream读取对象close()
:关闭流
使用对象流的条件
要使一个Java对象可序列化,需要满足以下条件:
- 该类必须实现
java.io.Serializable
接口(这是一个标记接口,不包含任何方法)。 - 该类的所有属性都必须是可 序列化的,或者被标记为
transient
(表示该属性不参与序列化)。 - 对于不想被序列化的敏感数据,应该使用
transient
关键字标记。
import java.io.Serializable;
public class Person implements Serializable {
// 序列化版本号,用于版本控制
private static final long serialVersionUID = 1L;
private String name;
private int age;
// 不想被序列化的敏感信息
private transient String password;
// 构造器和getter/setter方法省略...
}
基本使用示例
下面我们通过一个简单的示例展示如何使用对象流进行序列化和反序列化:
import java.io.*;
public class ObjectStreamExample {
public static void main(String[] args) {
// 创建一个Person对象
Person person = new Person("张三", 25);
person.setPassword("123456");
// 序列化
try (FileOutputStream fileOut = new FileOutputStream("person.ser");
ObjectOutputStream out = new ObjectOutputStream(fileOut)) {
out.writeObject(person);
System.out.println("Person对象已成功序列化到person.ser");
} catch (IOException e) {
e.printStackTrace();
}
// 反序列化
try (FileInputStream fileIn = new FileInputStream("person.ser");
ObjectInputStream in = new ObjectInputStream(fileIn)) {
Person deserializedPerson = (Person) in.readObject();
System.out.println("Person对象已从person.ser反序列化");
System.out.println("姓名: " + deserializedPerson.getName());
System.out.println("年龄: " + deserializedPerson.getAge());
System.out.println("密码: " + deserializedPerson.getPassword());
} catch (IOException | ClassNotFoundException e) {
e.printStackTrace();
}
}
}
输出结果:
Person对象已成功序列化到person.ser
Person对象已从person.ser反序列化
姓名: 张三
年龄: 25
密码: null // 注意:password被标记为transient,所以反序列化后为null
注意
- 序列化过程中,被标记为
transient
的字段不会被序列化,反序列化后这些字段的值将是默认值(对于对象是null,对于基本类型是0或false)。 - 使用对象流时,必须使用try-with-resources或在finally块中关闭流,以防止资源泄漏。
序列化版本控制
在对象序列化中,序列化版本ID(serialVersionUID)用于确保序列化和反序列化的兼容性。当一个类被序列化后,如果这个类的结构发生了变化,那么反序列化时可能会失败,除非serialVersionUID保持不变。
private static final long serialVersionUID = 1L;
如果没有显式定义serialVersionUID,Java会根据类的结构自动生成一个,但这种方式在 类结构变化时很容易导致不兼容。因此,强烈建议为可序列化的类显式定义serialVersionUID。
自定义序列化过程
对于某些复杂对象,我们可能需要自定义序列化过程。Java提供了两种方法:
- 实现
writeObject()
和readObject()
方法 - 实现
Externalizable
接口
使用writeObject和readObject方法
import java.io.*;
public class CustomPerson implements Serializable {
private static final long serialVersionUID = 1L;
private String name;
private int age;
private String password;
// 自定义序列化方法
private void writeObject(ObjectOutputStream out) throws IOException {
out.defaultWriteObject(); // 执行默认序列化
// 加密密码后再写入
out.writeObject("encrypted:" + password);
}
// 自定义反序列化方法
private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
in.defaultReadObject(); // 执行默认反序列化
// 读取加密的密码并解密
String encryptedPassword = (String) in.readObject();
if (encryptedPassword.startsWith("encrypted:")) {
this.password = encryptedPassword.substring(10);
}
}
// 构造器和getter/setter方法省略...
}
实现Externalizable接口
import java.io.*;
public class ExternalizablePerson implements Externalizable {
private static final long serialVersionUID = 1L;
private String name;
private int age;
private String password;
// 必须提供无参构造器
public ExternalizablePerson() {}
// 手动指定如何序列化
@Override
public void writeExternal(ObjectOutput out) throws IOException {
out.writeObject(name);
out.writeInt(age);
// 密码不序列化
}
// 手动指定如何反序列化
@Override
public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
this.name = (String) in.readObject();
this.age = in.readInt();
this.password = null; // 密码不恢复
}
// 构造器和getter/setter方法省略...
}
提示
实现Externalizable
接口时,必须提供一个公共的无参构造器,因为反序列化时会先调用该构造器创建对象实例,再调用readExternal()
方法。