Java序列化详解
什么是序列化
序列化是将对象转换为字节序列的过程,反序列化是将字节序列还原为对象的过程。Java通过Serializable接口实现序列化。
基本序列化
import java.io.Serializable;
public class Employee implements Serializable {
private static final long serialVersionUID = 1L;
private String name;
private int age;
private double salary;
public Employee(String name, int age, double salary) {
this.name = name;
this.age = age;
this.salary = salary;
}
@Override
public String toString() {
return "Employee{name='" + name + "', age=" + age + ", salary=" + salary + "}";
}
}
序列化和反序列化
import java.io.*;
public class SerializationDemo {
public static void main(String[] args) {
String fileName = "employee.ser";
Employee emp = new Employee("张三", 30, 50000.0);
System.out.println("原始对象: " + emp);
try (ObjectOutputStream oos = new ObjectOutputStream(
new FileOutputStream(fileName))) {
oos.writeObject(emp);
System.out.println("序列化成功");
} catch (IOException e) {
e.printStackTrace();
}
try (ObjectInputStream ois = new ObjectInputStream(
new FileInputStream(fileName))) {
Employee deserializedEmp = (Employee) ois.readObject();
System.out.println("反序列化对象: " + deserializedEmp);
System.out.println("是否同一对象: " + (emp == deserializedEmp));
} catch (IOException | ClassNotFoundException e) {
e.printStackTrace();
}
}
}
transient关键字
transient关键字用于排除不需要序列化的字段。
import java.io.Serializable;
public class User implements Serializable {
private static final long serialVersionUID = 1L;
private String username;
private transient String password;
private int age;
public User(String username, String password, int age) {
this.username = username;
this.password = password;
this.age = age;
}
public String getPassword() {
return password;
}
@Override
public String toString() {
return "User{username='" + username + "', password='" + password + "', age=" + age + "}";
}
}
Externalizable接口
Externalizable接口提供更精细的序列化控制。
import java.io.Externalizable;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectOutput;
public class Product implements Externalizable {
private String name;
private double price;
private int stock;
public Product() {
}
public Product(String name, double price, int stock) {
this.name = name;
this.price = price;
this.stock = stock;
}
@Override
public void writeExternal(ObjectOutput out) throws IOException {
out.writeUTF(name);
out.writeDouble(price);
}
@Override
public void readExternal(ObjectInput in) throws IOException {
name = in.readUTF();
price = in.readDouble();
}
@Override
public String toString() {
return "Product{name='" + name + "', price=" + price + ", stock=" + stock + "}";
}
}
序列化集合
import java.io.*;
import java.util.ArrayList;
import java.util.List;
public class CollectionSerializationDemo {
public static void main(String[] args) {
List<Employee> employees = new ArrayList<>();
employees.add(new Employee("张三", 25, 30000));
employees.add(new Employee("李四", 30, 40000));
employees.add(new Employee("王五", 35, 50000));
String fileName = "employees.ser";
try (ObjectOutputStream oos = new ObjectOutputStream(
new FileOutputStream(fileName))) {
oos.writeObject(employees);
System.out.println("集合序列化成功");
} catch (IOException e) {
e.printStackTrace();
}
try (ObjectInputStream ois = new ObjectInputStream(
new FileInputStream(fileName))) {
@SuppressWarnings("unchecked")
List<Employee> deserialized = (List<Employee>) ois.readObject();
System.out.println("反序列化集合:");
deserialized.forEach(System.out::println);
} catch (IOException | ClassNotFoundException e) {
e.printStackTrace();
}
}
}
serialVersionUID
serialVersionUID用于版本控制,确保序列化和反序列化的兼容性。
import java.io.Serializable;
public class Config implements Serializable {
private static final long serialVersionUID = 2L;
private String host;
private int port;
private transient String password;
public Config(String host, int port, String password) {
this.host = host;
this.port = port;
this.password = password;
}
@Override
public String toString() {
return "Config{host='" + host + "', port=" + port + "}";
}
}
自定义序列化
import java.io.Serializable;
public class Person implements Serializable {
private static final long serialVersionUID = 1L;
private String name;
private int age;
private String email;
public Person(String name, int age, String email) {
this.name = name;
this.age = age;
this.email = email;
}
private void writeObject(java.io.ObjectOutputStream out) throws java.io.IOException {
out.writeUTF(name);
out.writeInt(age);
out.writeUTF(maskEmail(email));
}
private void readObject(java.io.ObjectInputStream in) throws java.io.IOException, ClassNotFoundException {
name = in.readUTF();
age = in.readInt();
email = in.readUTF();
}
private String maskEmail(String email) {
if (email == null) return null;
int atIndex = email.indexOf("@");
if (atIndex > 0) {
return email.charAt(0) + "***" + email.substring(atIndex);
}
return "***";
}
@Override
public String toString() {
return "Person{name='" + name + "', age=" + age + ", email='" + email + "'}";
}
}
序列化安全
- 敏感数据使用transient修饰
- 实现Serializable时指定serialVersionUID
- 反序列化时验证对象类型
- 考虑使用JSON等更安全的序列化方式
import java.io.Serializable;
public class SecureData implements Serializable {
private static final long serialVersionUID = 1L;
private String data;
private transient String sensitiveData;
private void readObject(java.io.ObjectInputStream in)
throws java.io.IOException, ClassNotFoundException {
in.defaultReadObject();
ObjectInputStream.GetField fields = in.readFields();
String receivedData = (String) fields.get("data", null);
if (receivedData == null || receivedData.isEmpty()) {
throw new java.io.InvalidObjectException("数据不能为空");
}
}
}
序列化最佳实践
- 始终指定serialVersionUID
- 使用transient保护敏感信息
- 考虑序列化性能影响
- 对于复杂场景考虑使用JSON/Protobuf
- 注意反序列化漏洞
总结
Java序列化是对象持久化的重要机制。理解Serializable接口、transient关键字和版本控制能帮助你安全高效地实现对象的序列化和反序列化。