ZetCode

Java ObjectOutputStream 类

最后修改时间:2025 年 4 月 16 日

java.io.ObjectOutputStream 类将基本数据类型和对象写入 OutputStream。 它用于 Java 对象序列化,将对象转换为字节流。 序列化的对象可以使用 ObjectInputStream 重新构造。

ObjectOutputStream 实现了 ObjectOutputObjectStreamConstants 接口。 它处理基本类型和对象图。 只有实现 Serializable 的对象才能被写入。 流包括用于正确反序列化的类型信息。

ObjectOutputStream 类概述

ObjectOutputStream 提供了将对象和基本类型写入流的方法。 它维护对先前写入对象的引用以处理循环引用。 该类写入一个流头,该流头必须与 ObjectInputStream 匹配。

public class ObjectOutputStream extends OutputStream 
    implements ObjectOutput, ObjectStreamConstants {
    public ObjectOutputStream(OutputStream out);
    public final void writeObject(Object obj);
    public void writeInt(int val);
    public void writeUTF(String str);
    public void defaultWriteObject();
    public void reset();
    public void close();
}

上面的代码展示了 ObjectOutputStream 提供的关键方法。 这些方法允许将对象和基本类型写入输出流。 该类根据 Java 的序列化协议处理对象序列化。

创建 ObjectOutputStream

ObjectOutputStream 通过将其包装在另一个 OutputStream 中创建。 构造函数写入一个流头,该流头必须存在于反序列化中。 完成后,始终关闭流以确保写入所有数据。

Main.java
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;

public class Main {

    public static void main(String[] args) {
        try {
            // Create a FileOutputStream
            FileOutputStream fileOut = new FileOutputStream("object.dat");
            
            // Wrap it in ObjectOutputStream
            ObjectOutputStream objectOut = new ObjectOutputStream(fileOut);
            
            System.out.println("ObjectOutputStream created successfully");
            
            // Always close the stream
            objectOut.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

此示例演示了基本的 ObjectOutputStream 创建。 FileOutputStream 提供了底层的字节流。 ObjectOutputStream 构造函数可能会抛出 IOException,如果它无法写入流头。 始终在 finally 块中关闭流或使用 try-with-resources。

写入基本类型

ObjectOutputStream 提供了用于写入所有 Java 基本类型的方法。 这些方法以与平台无关的格式写入数据。 可以使用 ObjectInputStream 中的相应方法将数据读回。

Main.java
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;

public class Main {

    public static void main(String[] args) {
        try (ObjectOutputStream oos = 
                new ObjectOutputStream(new FileOutputStream("primitives.dat"))) {
            
            // Write various primitive types
            oos.writeBoolean(true);
            oos.writeByte((byte) 65);
            oos.writeChar('A');
            oos.writeDouble(3.14159);
            oos.writeFloat(2.718f);
            oos.writeInt(42);
            oos.writeLong(123456789L);
            oos.writeShort((short) 1000);
            
            System.out.println("Primitives written successfully");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

此示例展示了将不同的基本类型写入文件。 try-with-resources 语句确保了正确的流关闭。 每个写入方法处理一个特定的基本类型。 数据以保留类型信息的二进制格式写入。

序列化对象

ObjectOutputStream 的主要目的是写入对象。 对象的类必须实现 Serializable。 writeObject 方法处理整个对象图,包括引用的对象。 transient 字段不会被序列化。

Person.java
import java.io.Serializable;

public class Person implements Serializable {
    private String name;
    private int age;
    private transient String password; // Won't be serialized
    
    public Person(String name, int age, String password) {
        this.name = name;
        this.age = age;
        this.password = password;
    }
    
    @Override
    public String toString() {
        return "Person [name=" + name + ", age=" + age + "]";
    }
}
Main.java
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;

public class Main {

    public static void main(String[] args) {
        Person person = new Person("Alice", 30, "secret123");
        
        try (ObjectOutputStream oos = 
                new ObjectOutputStream(new FileOutputStream("person.dat"))) {
            
            oos.writeObject(person);
            System.out.println("Person object serialized: " + person);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

此示例演示了对象序列化。 Person 类实现了 Serializable。 密码字段被标记为 transient,不会被保存。 writeObject 方法序列化整个对象。 反序列化将使用其字段值重新创建对象。

写入数组和集合

ObjectOutputStream 可以序列化数组和标准集合。 所有元素都必须是可序列化的。 流保留了集合和数组的结构。 嵌套集合被递归处理。

Main.java
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

public class Main {

    public static void main(String[] args) {
        // Create sample data
        int[] numbers = {1, 2, 3, 4, 5};
        List<String> names = new ArrayList<>(
            Arrays.asList("Alice", "Bob", "Charlie"));
        
        try (ObjectOutputStream oos = 
                new ObjectOutputStream(new FileOutputStream("collections.dat"))) {
            
            // Write array
            oos.writeObject(numbers);
            
            // Write collection
            oos.writeObject(names);
            
            System.out.println("Array and collection serialized");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

此示例展示了数组和 ArrayList 的序列化。 数组和集合都实现了 Serializable。 流保留了确切的结构和内容。 在反序列化期间,对象将被重建,并具有相同的内容。

使用 writeObject 自定义序列化

类可以通过实现 writeObject 来定义自定义序列化。 此方法必须是私有的,并处理写入对象的状态。 它通常首先调用 defaultWriteObject,然后写入其他数据。

Account.java
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.util.Date;

public class Account implements Serializable {
    private String accountNumber;
    private double balance;
    private Date lastTransaction;
    
    public Account(String accountNumber, double balance) {
        this.accountNumber = accountNumber;
        this.balance = balance;
        this.lastTransaction = new Date();
    }
    
    private void writeObject(ObjectOutputStream out) throws IOException {
        out.defaultWriteObject(); // Serialize normal fields
        out.writeUTF(accountNumber.substring(0, 2) + "XXXXXX"); // Mask number
    }
    
    // readObject would be needed for deserialization
    
    @Override
    public String toString() {
        return "Account [number=" + accountNumber + ", balance=" + balance + "]";
    }
}
Main.java
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;

public class Main {

    public static void main(String[] args) {
        Account account = new Account("1234567890", 1000.0);
        
        try (ObjectOutputStream oos = 
                new ObjectOutputStream(new FileOutputStream("account.dat"))) {
            
            oos.writeObject(account);
            System.out.println("Account serialized with custom writeObject");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

此示例演示了自定义序列化。 Account 类实现了 writeObject 以掩盖帐号。 defaultWriteObject 处理正常的字段序列化。 可以在调用 defaultWriteObject 之后添加自定义数据。 反序列化需要一个匹配的 readObject 方法。

重置流

reset 方法清除对象缓存,允许将重复的对象写入为新对象。 这在发送相同对象的多个副本时很有用。 如果没有 reset,后续的写入将引用第一个实例。

Main.java
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;

public class Main {

    public static void main(String[] args) {
        String sharedString = "Shared String";
        
        try (ObjectOutputStream oos = 
                new ObjectOutputStream(new FileOutputStream("reset.dat"))) {
            
            // Write first instance
            oos.writeObject(sharedString);
            
            // Write second instance (will be treated as reference)
            oos.writeObject(sharedString);
            
            // Reset the stream
            oos.reset();
            
            // Write third instance (new copy)
            oos.writeObject(sharedString);
            
            System.out.println("Objects written with reset");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

此示例展示了 reset 对对象序列化的影响。 前两次对 sharedString 的写入创建一个对象加上一个引用。 在 reset 之后,第三次写入创建一个新的副本。 此行为对于每个写入都应该独立的网络协议非常重要。

来源

Java ObjectOutputStream 类文档

在本文中,我们介绍了 Java ObjectOutputStream 类的基本方法和特性。 了解对象序列化对于 Java 应用程序中的持久性和网络通信至关重要。

作者

我的名字是 Jan Bodnar,我是一名经验丰富的程序员,在这一领域拥有多年的经验。 我于 2007 年开始撰写编程文章,此后撰写了 1,400 多篇文章和 8 本电子书。 凭借超过八年的教学经验,我致力于分享我的知识并帮助他人掌握编程概念。

列出所有Java教程