transient 关键字
概述
transient
关键字用于标记不需要序列化的成员变量。当对象被序列化时,标记为transient的字段会被忽略,不会写入到序列化流中,反序列化时这些字段会被赋予默认值。
语法格式
private transient int field1;
private transient String field2;
private transient Object field3;
序列化基础
什么是序列化
序列化是将对象转换为字节流的过程,以便存储到文件、数据库或通过网络传输。反序列化是将字节流恢复为对象的过程。
Java序列化机制
Java通过Serializable
接口实现序列化,transient关键字允许开发者控制哪些字段参与序列化。
基本用法
基础示例
import java.io.*;
public class User implements Serializable {
private static final long serialVersionUID = 1L;
private String username; // 会被序列化
private String email; // 会被序列化
private transient String password; // 不会被序列化(安全考虑)
private transient int sessionId; // 不会被序列化(临时数据)
public User(String username, String email, String password, int sessionId) {
this.username = username;
this.email = email;
this.password = password;
this.sessionId = sessionId;
}
// Getter和Setter方法
public String getUsername() { return username; }
public void setUsername(String username) { this.username = username; }
public String getEmail() { return email; }
public void setEmail(String email) { this.email = email; }
public String getPassword() { return password; }
public void setPassword(String password) { this.password = password; }
public int getSessionId() { return sessionId; }
public void setSessionId(int sessionId) { this.sessionId = sessionId; }
@Override
public String toString() {
return "User{" +
"username='" + username + '\'' +
", email='" + email + '\'' +
", password='" + password + '\'' +
", sessionId=" + sessionId +
'}';
}
}
// 测试序列化和反序列化
public class TransientExample {
public static void main(String[] args) {
User originalUser = new User("alice", "alice@example.com", "secret123", 12345);
System.out.println("原始对象: " + originalUser);
// 序列化
try {
FileOutputStream fos = new FileOutputStream("user.ser");
ObjectOutputStream oos = new ObjectOutputStream(fos);
oos.writeObject(originalUser);
oos.close();
fos.close();
System.out.println("序列化完成");
} catch (IOException e) {
e.printStackTrace();
}
// 反序列化
try {
FileInputStream fis = new FileInputStream("user.ser");
ObjectInputStream ois = new ObjectInputStream(fis);
User deserializedUser = (User) ois.readObject();
ois.close();
fis.close();
System.out.println("反序列化后: " + deserializedUser);
System.out.println("密码是否丢失: " + (deserializedUser.getPassword() == null));
System.out.println("会话ID值: " + deserializedUser.getSessionId()); // 应该是0(int默认值)
} catch (IOException | ClassNotFoundException e) {
e.printStackTrace();
}
}
}
安全敏感信息
import java.io.*;
public class BankAccount implements Serializable {
private static final long serialVersionUID = 1L;
private String accountNumber;
private String accountHolderName;
private double balance;
// 敏感信息不序列化
private transient String pin; // PIN码
private transient String socialSecurityNumber; // 社会保险号
private transient String secretKey; // 加密密钥
// 临时计算字段不序列化
private transient double interestEarned; // 利息收入(可重新计算)
private transient long lastAccessTime; // 最后访问时间(运行时确定)
public BankAccount(String accountNumber, String accountHolderName,
double balance, String pin, String ssn) {
this.accountNumber = accountNumber;
this.accountHolderName = accountHolderName;
this.balance = balance;
this.pin = pin;
this.socialSecurityNumber = ssn;
this.secretKey = generateSecretKey();
this.lastAccessTime = System.currentTimeMillis();
this.interestEarned = calculateInterest();
}
private String generateSecretKey() {
return "SECRET_" + System.currentTimeMillis();
}
private double calculateInterest() {
return balance * 0.02; // 2%利率
}
// 反序列化后的初始化方法
private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException {
ois.defaultReadObject(); // 先执行默认反序列化
// 重新初始化transient字段
this.secretKey = generateSecretKey();
this.lastAccessTime = System.currentTimeMillis();
this.interestEarned = calculateInterest();
System.out.println("反序列化后重新初始化敏感和临时字段");
}
public void displayInfo() {
System.out.println("账户信息:");
System.out.println(" 账号: " + accountNumber);
System.out.println(" 持有人: " + accountHolderName);
System.out.println(" 余额: $" + balance);
System.out.println(" PIN: " + (pin != null ? "已设置" : "未设置"));
System.out.println(" SSN: " + (socialSecurityNumber != null ? "已设置" : "未设置"));
System.out.println(" 密钥: " + (secretKey != null ? secretKey : "未设置"));
System.out.println(" 利息收入: $" + interestEarned);
System.out.println(" 最后访问: " + new java.util.Date(lastAccessTime));
}
// Getter和Setter方法
public String getAccountNumber() { return accountNumber; }
public String getAccountHolderName() { return accountHolderName; }
public double getBalance() { return balance; }
public void setBalance(double balance) {
this.balance = balance;
this.interestEarned = calculateInterest(); // 重新计算利息
}
public void setPin(String pin) { this.pin = pin; }
public boolean verifyPin(String inputPin) {
return pin != null && pin.equals(inputPin);
}
}
// 测试银行账户序列化
public class BankAccountTest {
public static void main(String[] args) {
BankAccount account = new BankAccount(
"123456789",
"John Doe",
10000.00,
"1234",
"555-12-3456"
);
System.out.println("=== 序列化前 ===");
account.displayInfo();
// 序列化
try {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baos);
oos.writeObject(account);
byte[] serializedData = baos.toByteArray();
oos.close();
System.out.println("\n=== 序列化成功,数据大小: " + serializedData.length + " bytes ===");
// 反序列化
ByteArrayInputStream bais = new ByteArrayInputStream(serializedData);
ObjectInputStream ois = new ObjectInputStream(bais);
BankAccount deserializedAccount = (BankAccount) ois.readObject();
ois.close();
System.out.println("\n=== 反序列化后 ===");
deserializedAccount.displayInfo();
// 验证PIN(应该失败,因为PIN是transient的)
System.out.println("\n=== PIN验证测试 ===");
System.out.println("验证PIN '1234': " + deserializedAccount.verifyPin("1234"));
} catch (IOException | ClassNotFoundException e) {
e.printStackTrace();
}
}
}
缓存和计算字段
import java.io.*;
import java.util.ArrayList;
import java.util.List;
public class ShoppingCart implements Serializable {
private static final long serialVersionUID = 1L;
private List<Item> items;
private String customerId;
// 计算字段 - 不需要序列化,可以重新计算
private transient double totalPrice;
private transient int totalItems;
private transient double averageItemPrice;
// 缓存字段 - 不需要序列化
private transient String formattedTotal;
private transient boolean isCalculated;
public ShoppingCart(String customerId) {
this.customerId = customerId;
this.items = new ArrayList<>();
this.isCalculated = false;
}
public void addItem(Item item) {
items.add(item);
invalidateCache(); // 使缓存失效
}
public void removeItem(Item item) {
items.remove(item);
invalidateCache();
}
private void invalidateCache() {
isCalculated = false;
formattedTotal = null;
}
private void calculateTotals() {
if (isCalculated) return;
totalPrice = 0;
totalItems = items.size();
for (Item item : items) {
totalPrice += item.getPrice() * item.getQuantity();
}
averageItemPrice = totalItems > 0 ? totalPrice / totalItems : 0;
formattedTotal = String.format("$%.2f", totalPrice);
isCalculated = true;
System.out.println("重新计算购物车统计信息");
}
// 反序列化后重新计算
private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException {
ois.defaultReadObject();
// 重新初始化transient字段
invalidateCache();
calculateTotals();
System.out.println("购物车反序列化完成,重新计算统计信息");
}
public double getTotalPrice() {
calculateTotals();
return totalPrice;
}
public int getTotalItems() {
calculateTotals();
return totalItems;
}
public double getAverageItemPrice() {
calculateTotals();
return averageItemPrice;
}
public String getFormattedTotal() {
calculateTotals();
return formattedTotal;
}
public void displayCart() {
System.out.println("购物车 (客户ID: " + customerId + "):");
for (Item item : items) {
System.out.println(" " + item);
}
System.out.println("总价: " + getFormattedTotal());
System.out.println("商品数量: " + getTotalItems());
System.out.println("平均价格: $" + String.format("%.2f", getAverageItemPrice()));
}
// 静态内部类
public static class Item implements Serializable {
private static final long serialVersionUID = 1L;
private String name;
private double price;
private int quantity;
public Item(String name, double price, int quantity) {
this.name = name;
this.price = price;
this.quantity = quantity;
}
public String getName() { return name; }
public double getPrice() { return price; }
public int getQuantity() { return quantity; }
@Override
public String toString() {
return name + " (价格: $" + price + ", 数量: " + quantity + ")";
}
}
}
// 测试购物车序列化
public class ShoppingCartTest {
public static void main(String[] args) {
ShoppingCart cart = new ShoppingCart("USER123");
// 添加商品
cart.addItem(new ShoppingCart.Item("笔记本电脑", 999.99, 1));
cart.addItem(new ShoppingCart.Item("鼠标", 29.99, 2));
cart.addItem(new ShoppingCart.Item("键盘", 79.99, 1));
System.out.println("=== 序列化前 ===");
cart.displayCart();
// 序列化
try {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baos);
oos.writeObject(cart);
byte[] serializedData = baos.toByteArray();
oos.close();
System.out.println("\n=== 序列化成功 ===");
// 反序列化
ByteArrayInputStream bais = new ByteArrayInputStream(serializedData);
ObjectInputStream ois = new ObjectInputStream(bais);
ShoppingCart deserializedCart = (ShoppingCart) ois.readObject();
ois.close();
System.out.println("\n=== 反序列化后 ===");
deserializedCart.displayCart();
} catch (IOException | ClassNotFoundException e) {
e.printStackTrace();
}
}
}
线程相关字段
import java.io.*;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.locks.ReentrantLock;
public class TaskProcessor implements Serializable {
private static final long serialVersionUID = 1L;
private String processorName;
private int maxConcurrentTasks;
// 线程相关字段不应该被序列化
private transient Thread workerThread;
private transient ReentrantLock lock;
private transient AtomicLong processedCount;
private transient volatile boolean isRunning;
// 状态信息(会被序列化)
private long totalProcessedTasks;
private long creationTime;
public TaskProcessor(String processorName, int maxConcurrentTasks) {
this.processorName = processorName;
this.maxConcurrentTasks = maxConcurrentTasks;
this.totalProcessedTasks = 0;
this.creationTime = System.currentTimeMillis();
initializeTransientFields();
}
private void initializeTransientFields() {
this.lock = new ReentrantLock();
this.processedCount = new AtomicLong(totalProcessedTasks);
this.isRunning = false;
System.out.println("初始化线程相关字段");
}
// 反序列化后重新初始化transient字段
private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException {
ois.defaultReadObject();
initializeTransientFields();
System.out.println("TaskProcessor反序列化完成,重新初始化线程相关字段");
}
public void startProcessing() {
lock.lock();
try {
if (isRunning) {
System.out.println("处理器已在运行中");
return;
}
isRunning = true;
workerThread = new Thread(() -> {
System.out.println(processorName + " 开始处理任务");
while (isRunning) {
try {
// 模拟任务处理
Thread.sleep(1000);
long count = processedCount.incrementAndGet();
totalProcessedTasks = count;
System.out.println(processorName + " 处理了任务 #" + count);
if (count >= 5) { // 处理5个任务后停止
isRunning = false;
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
break;
}
}
System.out.println(processorName + " 停止处理任务");
}, processorName + "-Worker");
workerThread.start();
} finally {
lock.unlock();
}
}
public void stopProcessing() {
lock.lock();
try {
isRunning = false;
if (workerThread != null) {
workerThread.interrupt();
}
} finally {
lock.unlock();
}
}
public void waitForCompletion() {
if (workerThread != null) {
try {
workerThread.join();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}
public void displayStatus() {
System.out.println("处理器状态:");
System.out.println(" 名称: " + processorName);
System.out.println(" 最大并发任务: " + maxConcurrentTasks);
System.out.println(" 总处理任务数: " + totalProcessedTasks);
System.out.println(" 创建时间: " + new java.util.Date(creationTime));
System.out.println(" 当前运行状态: " + (isRunning ? "运行中" : "已停止"));
System.out.println(" 工作线程: " + (workerThread != null ? workerThread.getName() : "未创建"));
}
public String getProcessorName() { return processorName; }
public long getTotalProcessedTasks() { return totalProcessedTasks; }
public boolean isRunning() { return isRunning; }
}
// 测试任务处理器序列化
public class TaskProcessorTest {
public static void main(String[] args) {
TaskProcessor processor = new TaskProcessor("MainProcessor", 3);
System.out.println("=== 启动处理器 ===");
processor.displayStatus();
processor.startProcessing();
processor.waitForCompletion();
System.out.println("\n=== 处理完成后 ===");
processor.displayStatus();
// 序列化
try {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baos);
oos.writeObject(processor);
byte[] serializedData = baos.toByteArray();
oos.close();
System.out.println("\n=== 序列化成功 ===");
// 反序列化
ByteArrayInputStream bais = new ByteArrayInputStream(serializedData);
ObjectInputStream ois = new ObjectInputStream(bais);
TaskProcessor deserializedProcessor = (TaskProcessor) ois.readObject();
ois.close();
System.out.println("\n=== 反序列化后 ===");
deserializedProcessor.displayStatus();
// 测试反序列化后的功能
System.out.println("\n=== 测试反序列化后的功能 ===");
deserializedProcessor.startProcessing();
deserializedProcessor.waitForCompletion();
deserializedProcessor.displayStatus();
} catch (IOException | ClassNotFoundException e) {
e.printStackTrace();
}
}
}
使用场景
1. 敏感信息保护
public class LoginSession implements Serializable {
private String userId;
private long loginTime;
private transient String password; // 密码不序列化
private transient String sessionToken; // 会话令牌不序列化
}
2. 临时计算字段
public class Rectangle implements Serializable {
private double width;
private double height;
private transient double area; // 面积可以重新计算
private transient double perimeter; // 周长可以重新计算
}
3. 系统资源
public class DatabaseConnection implements Serializable {
private String connectionString;
private transient Connection connection; // 数据库连接不能序列化
private transient PreparedStatement statement;
}
4. 缓存数据
public class UserProfile implements Serializable {
private String username;
private String email;
private transient String cachedDisplayName; // 缓存的显示名
private transient long cacheTimestamp; // 缓存时间戳
}
最佳实践
安全考虑:
- 将密码、密钥等敏感信息标记为transient
- 考虑使用自定义序列化方法进一步控制
性能优化:
- 大型对象或集合如果可以重新获取,可以标记为transient
- 临时计算结果不需要序列化
资源管理:
- 文件句柄、网络连接、线程等系统资源必须是transient
- 在反序列化后重新初始化这些资源
与serialVersionUID配合:
public class MyClass implements Serializable { private static final long serialVersionUID = 1L; private String data; private transient String cache; }
注意事项
默认值恢复:
- transient字段在反序列化后会被设置为默认值
- 对象引用为null,数值类型为0,布尔类型为false
继承影响:
- 子类中的transient字段不会影响父类的序列化
- 父类的transient字段在子类序列化时仍然被忽略
static字段:
- static字段本身就不参与序列化
- 不需要同时使用static和transient
自定义序列化:
private void writeObject(ObjectOutputStream oos) throws IOException { oos.defaultWriteObject(); // 自定义序列化逻辑 } private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException { ois.defaultReadObject(); // 重新初始化transient字段 }
transient关键字是Java序列化机制中的重要组成部分,正确使用它能够提高安全性、性能和代码的健壮性。