Java 中的序列化(Serialization)是将对象转换为字节流的过程,用于持久化存储(如写入文件、数据库)或网络传输(如RPC调用);反序列化(Deserialization)则是将字节流恢复为对象的过程。Serializable
接口是 Java 提供的标记接口(无方法),用于标识一个类的实例可以被序列化。以下是详细说明、核心规则及典型示例。
byte[]
),可存储到文件、数据库或通过网络传输。Serializable
接口NotSerializableException
。static
和 transient
修饰的字段外)自动生成序列化逻辑。transient
关键字null
、0
、false
等)。static
字段serialVersionUID
版本号serialVersionUID
是否与当前类的 serialVersionUID
一致:
InvalidClassException
(如类的字段增删、类型修改)。serialVersionUID
(避免因类结构变化导致反序列化失败)。writeObject(ObjectOutputStream out)
和 readObject(ObjectInputStream in)
方法实现。定义一个可序列化的 User
类,演示对象写入文件并读取恢复的过程。
步骤1:定义可序列化的类
显式声明 serialVersionUID
,并包含普通字段、transient
字段和 static
字段。
java 复制
import java.io.Serializable; public class User implements Serializable { // 显式声明 serialVersionUID(推荐) private static final long serialVersionUID = 1L; private String name; // 普通字段(会被序列化) private transient int age; // transient 字段(不会被序列化) private static String role = "guest"; // static 字段(不会被序列化) public User(String name, int age) { this.name = name; this.age = age; } @Override public String toString() { return "User{name='" + name + "', age=" + age + ", role='" + role + "'}"; } }
步骤2:序列化对象到文件
使用 ObjectOutputStream
将 User
对象写入文件。
java 复制
import java.io.FileOutputStream; import java.io.ObjectOutputStream; import java.io.IOException; public class SerializeDemo { public static void main(String[] args) { User user = new User("Alice", 25); // 序列化:对象 → 字节流 → 文件 try (FileOutputStream fos = new FileOutputStream("user.ser"); ObjectOutputStream oos = new ObjectOutputStream(fos)) { oos.writeObject(user); // 写入对象 System.out.println("序列化完成,对象:" + user); } catch (IOException e) { e.printStackTrace(); } } }
步骤3:从文件反序列化对象
使用 ObjectInputStream
从文件读取字节流并恢复为 User
对象。
java 复制
import java.io.FileInputStream; import java.io.ObjectInputStream; import java.io.IOException; public class DeserializeDemo { public static void main(String[] args) { // 反序列化:文件 → 字节流 → 对象 try (FileInputStream fis = new FileInputStream("user.ser"); ObjectInputStream ois = new ObjectInputStream(fis)) { User restoredUser = (User) ois.readObject(); // 读取对象 System.out.println("反序列化完成,恢复的对象:" + restoredUser); // 输出说明:age 是 transient 字段(值为0),role 是 static 字段(值为当前类的 role) } catch (IOException | ClassNotFoundException e) { e.printStackTrace(); } } }
输出结果:
复制
序列化完成,对象:User{name='Alice', age=25, role='guest'} 反序列化完成,恢复的对象:User{name='Alice', age=0, role='guest'}
age
因 transient
修饰,反序列化后为默认值 0
;role
因 static
修饰,反序列化后恢复为类的当前静态值(未被序列化)。若对象包含其他可序列化对象的引用,序列化会递归处理所有关联对象(需确保所有关联对象也实现 Serializable
)。
定义 Address
类(可序列化):
java 复制
import java.io.Serializable; public class Address implements Serializable { private static final long serialVersionUID = 1L; private String city; public Address(String city) { this.city = city; } @Override public String toString() { return "Address{city='" + city + "'}"; } }
修改 User
类(包含 Address
引用):
java 复制
public class User implements Serializable { private static final long serialVersionUID = 1L; private String name; private transient int age; private Address address; // 引用可序列化对象 public User(String name, int age, Address address) { this.name = name; this.age = age; this.address = address; } @Override public String toString() { return "User{name='" + name + "', age=" + age + ", address=" + address + "}"; } }
序列化与反序列化测试:
java 复制
public class SerializeWithReferenceDemo { public static void main(String[] args) { Address addr = new Address("Beijing"); User user = new User("Bob", 30, addr); // 序列化 try (FileOutputStream fos = new FileOutputStream("user_with_ref.ser"); ObjectOutputStream oos = new ObjectOutputStream(fos)) { oos.writeObject(user); } catch (IOException e) { e.printStackTrace(); } // 反序列化 try (FileInputStream fis = new FileInputStream("user_with_ref.ser"); ObjectInputStream ois = new ObjectInputStream(fis)) { User restoredUser = (User) ois.readObject(); System.out.println("恢复的对象:" + restoredUser); // 输出:User{name='Bob', age=0, address=Address{city='Beijing'}} } catch (IOException | ClassNotFoundException e) { e.printStackTrace(); } } }
Address
对象会被递归序列化和反序列化,最终恢复完整的对象图。若需要对敏感字段(如密码)进行加密存储,可通过重写 writeObject
和 readObject
方法自定义序列化过程。
定义 SecureUser
类(自定义序列化):
java 复制
import java.io.*; import javax.crypto.Cipher; import javax.crypto.spec.SecretKeySpec; import java.util.Base64; public class SecureUser implements Serializable { private static final long serialVersionUID = 1L; private String username; private transient String password; // 敏感字段,不直接序列化 private static final String KEY = "1234567890123456"; // AES 密钥(16字节) public SecureUser(String username, String password) { this.username = username; this.password = password; } // 自定义序列化逻辑(加密密码) private void writeObject(ObjectOutputStream out) throws IOException { out.defaultWriteObject(); // 默认序列化非 transient 字段(username) try { // 加密密码 String encryptedPwd = encrypt(password); out.writeObject(encryptedPwd); // 写入加密后的密码 } catch (Exception e) { throw new IOException("加密失败", e); } } // 自定义反序列化逻辑(解密密码) private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException { in.defaultReadObject(); // 默认反序列化非 transient 字段(username) try { // 读取加密的密码并解密 String encryptedPwd = (String) in.readObject(); this.password = decrypt(encryptedPwd); } catch (Exception e) { throw new IOException("解密失败", e); } } // AES 加密 private String encrypt(String data) throws Exception { SecretKeySpec keySpec = new SecretKeySpec(KEY.getBytes(), "AES"); Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding"); cipher.init(Cipher.ENCRYPT_MODE, keySpec); byte[] encryptedBytes = cipher.doFinal(data.getBytes()); return Base64.getEncoder().encodeToString(encryptedBytes); } // AES 解密 private String decrypt(String encryptedData) throws Exception { SecretKeySpec keySpec = new SecretKeySpec(KEY.getBytes(), "AES"); Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding"); cipher.init(Cipher.DECRYPT_MODE, keySpec); byte[] decryptedBytes = cipher.doFinal(Base64.getDecoder().decode(encryptedData)); return new String(decryptedBytes); } @Override public String toString() { return "SecureUser{username='" + username + "', password='" + password + "'}"; } }
测试自定义序列化:
java
复制
public class CustomSerializeDemo { public static void main(String[] args) { SecureUser user = new SecureUser("admin", "p@ssw0rd"); // 序列化 try (FileOutputStream fos = new FileOutputStream("secure_user.ser"); ObjectOutputStream oos = new ObjectOutputStream(fos)) { oos.writeObject(user); } catch (IOException e) { e.printStackTrace(); } // 反序列化 try (FileInputStream fis = new FileInputStream("secure_user.ser"); ObjectInputStream ois = new ObjectInputStream(fis)) { SecureUser restoredUser = (SecureUser) ois.readObject(); System.out.println("恢复的对象:" + restoredUser); // 输出:SecureUser{username='admin', password='p@ssw0rd'}(密码被正确解密) } catch (IOException | ClassNotFoundException e) { e.printStackTrace(); } } }
password
被加密后存储,反序列化时自动解密,避免明文泄露。NotSerializableException
Serializable
接口。Serializable
接口。InvalidClassException
)serialVersionUID
与反序列化时的类 serialVersionUID
不同(如类的字段增删)。serialVersionUID
(如 private static final long serialVersionUID = 1L;
),避免自动生成。transient
字段虽不被序列化,但内存中的对象可能被反射获取;或默认序列化可能暴露敏感信息。transient
修饰,并结合自定义序列化逻辑(如加密)。readResolve()
方法避免: java 复制
protected Object readResolve() throws ObjectStreamException { return getInstance(); // 返回现有单例实例,而非新建 }
serialVersionUID
控制版本兼容。特性 | 说明 |
---|---|
核心接口 | Serializable (标记接口,无方法) |
序列化工具 | ObjectOutputStream (序列化)、ObjectInputStream (反序列化) |
关键规则 | transient 字段不序列化;static 字段不序列化;显式声明 serialVersionUID |
自定义逻辑 | 重写 writeObject 和 readObject 方法 |
适用场景 | 对象持久化(文件/数据库)、网络传输(RPC/HTTP) |
替代方案 | JSON(Jackson)、Protobuf(跨语言、高性能) |
最佳实践:
serialVersionUID
;transient
并结合加密;