ZetCode

Java ObjectInputStream 类

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

java.io.ObjectInputStream 类反序列化之前使用 ObjectOutputStream 写入的原始数据和对象。它从其序列化形式重建对象。该类实现了 ObjectInputObjectStreamConstants 接口。

ObjectInputStream 从底层输入流读取序列化数据。它处理对象图、循环引用和类版本控制。该类提供了从流中读取原始类型、对象和数组的方法。

ObjectInputStream 类概述

ObjectInputStream 扩展了 InputStream 并实现了对象反序列化。关键方法包括读取原始类型、对象以及通过 readObject 进行自定义反序列化。该类维护处理对象引用的状态。

public class ObjectInputStream 
    extends InputStream implements ObjectInput, ObjectStreamConstants {
    public ObjectInputStream(InputStream in) throws IOException;
    public final Object readObject() throws IOException, ClassNotFoundException;
    public int read() throws IOException;
    public int read(byte[] buf, int off, int len) throws IOException;
    public boolean readBoolean() throws IOException;
    public byte readByte() throws IOException;
    public char readChar() throws IOException;
    public double readDouble() throws IOException;
    public float readFloat() throws IOException;
    public int readInt() throws IOException;
    public long readLong() throws IOException;
    public short readShort() throws IOException;
    public String readUTF() throws IOException;
    public Object readUnshared() throws IOException, ClassNotFoundException;
    public void defaultReadObject() throws IOException, ClassNotFoundException;
    public void close() throws IOException;
}

上面的代码展示了 ObjectInputStream 提供的关键方法。这些方法允许从序列化流中读取原始类型和对象。该类自动处理对象重建和引用解析。

创建 ObjectInputStream

ObjectInputStream 是通过将其包装在另一个 InputStream 周围来创建的。如果流头无效,构造函数可能会抛出 IOException。完成操作后始终关闭流以释放资源。

Main.java
import java.io.FileInputStream;
import java.io.IOException;
import java.io.ObjectInputStream;

public class Main {

    public static void main(String[] args) {
        try {
            // Create from FileInputStream
            FileInputStream fileStream = new FileInputStream("objects.dat");
            ObjectInputStream objectStream = new ObjectInputStream(fileStream);
            
            System.out.println("ObjectInputStream created successfully");
            
            // Always close streams
            objectStream.close();
        } catch (IOException e) {
            System.err.println("Error creating ObjectInputStream: " + e.getMessage());
        }
    }
}

此示例演示了 ObjectInputStream 的基本创建。流被包装在从文件读取的 FileInputStream 周围。错误处理很重要,因为构造函数可能会抛出 IOException

读取原始类型

ObjectInputStream 提供了读取原始数据类型的方法。这些方法与 ObjectOutputStream 中的方法相对应。每种方法都从流中读取下一个原始值。

Main.java
import java.io.FileInputStream;
import java.io.IOException;
import java.io.ObjectInputStream;

public class Main {

    public static void main(String[] args) {
        try (ObjectInputStream ois = 
                new ObjectInputStream(new FileInputStream("primitives.dat"))) {
            
            // Read primitives in same order they were written
            boolean bool = ois.readBoolean();
            byte b = ois.readByte();
            char c = ois.readChar();
            double d = ois.readDouble();
            float f = ois.readFloat();
            int i = ois.readInt();
            long l = ois.readLong();
            short s = ois.readShort();
            String str = ois.readUTF();
            
            System.out.println("Read boolean: " + bool);
            System.out.println("Read byte: " + b);
            System.out.println("Read char: " + c);
            System.out.println("Read double: " + d);
            System.out.println("Read float: " + f);
            System.out.println("Read int: " + i);
            System.out.println("Read long: " + l);
            System.out.println("Read short: " + s);
            System.out.println("Read String: " + str);
            
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

此示例从文件读取原始值。这些值必须以写入的完全相同的顺序读取。try-with-resources 确保正确关闭流。每种读取方法都对应于特定的原始类型。

使用 readObject 读取对象

readObject 方法从序列化数据重建对象。对象的类必须实现 Serializable 并且在 JVM 中可用。该方法可能会抛出 ClassNotFoundException

Person.java
import java.io.Serializable;

public class Person implements Serializable {
    private String name;
    private int age;
    
    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }
    
    @Override
    public String toString() {
        return "Person{name='" + name + "', age=" + age + "}";
    }
}
Main.java
import java.io.FileInputStream;
import java.io.IOException;
import java.io.ObjectInputStream;

public class Main {

    public static void main(String[] args) {
        try (ObjectInputStream ois = 
                new ObjectInputStream(new FileInputStream("person.dat"))) {
            
            // Read object and cast to Person
            Person person = (Person) ois.readObject();
            System.out.println("Deserialized Person: " + person);
            
        } catch (IOException | ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
}

此示例演示了对象反序列化。Person 类必须实现 SerializablereadObject 方法返回一个 Object,必须将其强制转换为正确的类型。必须处理 IOExceptionClassNotFoundException

读取多个对象和数组

ObjectInputStream 可以从流中读取多个对象和数组。对象按照写入的顺序读取。可以使用 readObject 反序列化原始类型或对象的数组。

Main.java
import java.io.FileInputStream;
import java.io.IOException;
import java.io.ObjectInputStream;

public class Main {

    public static void main(String[] args) {
        try (ObjectInputStream ois = 
                new ObjectInputStream(new FileInputStream("objects.dat"))) {
            
            // Read multiple objects
            String message = (String) ois.readObject();
            int[] numbers = (int[]) ois.readObject();
            Person person = (Person) ois.readObject();
            
            System.out.println("Message: " + message);
            System.out.print("Numbers: ");
            for (int num : numbers) {
                System.out.print(num + " ");
            }
            System.out.println("\nPerson: " + person);
            
        } catch (IOException | ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
}

此示例从流中读取多个对象。这些对象必须以写入的相同顺序读取。该数组被强制转换为适当的类型。这里重用了前一个示例中的 Person 类。

使用 readUnshared 进行自定义反序列化

readUnshared 方法确保每次反序列化都返回一个唯一的对象。与 readObject 不同,它防止共享反序列化的对象。当必须保留对象身份时,这很有用。

Main.java
import java.io.FileInputStream;
import java.io.IOException;
import java.io.ObjectInputStream;

public class Main {

    public static void main(String[] args) {
        try (ObjectInputStream ois = 
                new ObjectInputStream(new FileInputStream("shared.dat"))) {
            
            // Read with readObject (may return shared reference)
            Object obj1 = ois.readObject();
            Object obj2 = ois.readObject();
            
            // Read with readUnshared (always new instance)
            Object obj3 = ois.readUnshared();
            Object obj4 = ois.readUnshared();
            
            System.out.println("readObject same instance? " + (obj1 == obj2));
            System.out.println("readUnshared same instance? " + (obj3 == obj4));
            
        } catch (IOException | ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
}

此示例比较了 readObjectreadUnshared。前者可能会为同一对象的多次读取返回共享引用。后者始终返回唯一实例。这会影响对象身份比较。

使用 serialVersionUID 处理版本控制

当类发展时,serialVersionUID 确保兼容性。它是序列化类的版本号。不匹配会导致 InvalidClassException。显式 UID 提供版本控制。

Main.java
import java.io.FileInputStream;
import java.io.IOException;
import java.io.ObjectInputStream;

public class Main {

    public static void main(String[] args) {
        try (ObjectInputStream ois = 
                new ObjectInputStream(new FileInputStream("versioned.dat"))) {
            
            VersionedObject obj = (VersionedObject) ois.readObject();
            System.out.println("Deserialized versioned object: " + obj);
            
        } catch (IOException | ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
}

class VersionedObject implements Serializable {
    private static final long serialVersionUID = 1L;  // Explicit version UID
    private String data;
    
    public VersionedObject(String data) {
        this.data = data;
    }
    
    @Override
    public String toString() {
        return "VersionedObject{data='" + data + "'}";
    }
}

此示例显示了一个具有显式 serialVersionUID 的类。当对类进行不兼容的更改时,应更新 UID。这可以防止旧的序列化实例反序列化期间的版本不匹配错误。

来源

Java ObjectInputStream 类文档

在本文中,我们介绍了 Java ObjectInputStream 类的基本方法和特性。理解这些概念对于在 Java 应用程序中使用对象序列化至关重要。

作者

我叫 Jan Bodnar,是一位经验丰富的程序员。我于 2007 年开始撰写编程文章,至今已撰写超过 1400 篇文章和 8 本电子书。凭借超过八年的教学经验,我致力于分享我的知识并帮助其他人掌握编程概念。

列出所有Java教程