ZetCode

Java InvalidClassException 类

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

当序列化或反序列化期间,序列化运行时检测到类出现问题时,会抛出 java.io.InvalidClassException 异常。 这些问题包括不兼容的类版本或缺失的 serialVersionUID。

当序列化类定义在序列化和反序列化之间发生更改时,通常会发生此异常。 JVM 使用 serialVersionUID 来验证兼容性。 如果没有显式声明,它会自动生成。

InvalidClassException 类概述

InvalidClassException 扩展自 ObjectStreamException 并指示序列化问题。 它通过其消息提供有关失败的详细信息。 通常包含类名和解释。

public class InvalidClassException extends ObjectStreamException {
    public String classname;
    public InvalidClassException(String reason);
    public InvalidClassException(String cname, String reason);
    public String getMessage();
}

上面的代码展示了 InvalidClassException 的结构。 classname 字段保存有问题的类名。 构造函数允许指定原因和类名。 getMessage 方法将两者组合在消息中。

基本序列化示例

此示例演示了基本序列化,其中没有发生异常。 我们将序列化和反序列化一个简单的 Person 对象。 该类实现了 Serializable 并声明了 serialVersionUID。

Main.java
import java.io.*;

class Person implements Serializable {
    private static final long serialVersionUID = 1L;
    String name;
    int age;
    
    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }
}

public class Main {
    public static void main(String[] args) {
        Person person = new Person("John Doe", 30);
        
        // Serialize
        try (ObjectOutputStream oos = new ObjectOutputStream(
                new FileOutputStream("person.ser"))) {
            oos.writeObject(person);
            System.out.println("Serialization complete");
        } catch (IOException e) {
            e.printStackTrace();
        }
        
        // Deserialize
        try (ObjectInputStream ois = new ObjectInputStream(
                new FileInputStream("person.ser"))) {
            Person deserialized = (Person) ois.readObject();
            System.out.println("Deserialized: " + deserialized.name);
        } catch (IOException | ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
}

此示例显示了成功的序列化和反序列化。 Person 类已声明了 serialVersionUID。 在不对该类进行更改的情况下,反序列化可以正常工作。 接下来,示例将展示导致 InvalidClassException 的场景。

缺少 serialVersionUID

当缺少 serialVersionUID 时,JVM 会根据类结构生成一个。 更改类结构会使生成的 UID 不兼容。 这会导致反序列化期间出现 InvalidClassException。

Main.java
import java.io.*;

// Version 1: Original class without serialVersionUID
class Product implements Serializable {
    String name;
    double price;
    
    public Product(String name, double price) {
        this.name = name;
        this.price = price;
    }
}

public class Main {
    public static void main(String[] args) {
        // Serialize original version
        Product product = new Product("Laptop", 999.99);
        try (ObjectOutputStream oos = new ObjectOutputStream(
                new FileOutputStream("product.ser"))) {
            oos.writeObject(product);
        } catch (IOException e) {
            e.printStackTrace();
        }
        
        // Simulate class change by adding a new field
        class Product implements Serializable {
            String name;
            double price;
            int quantity;  // Added field
            
            public Product(String name, double price) {
                this.name = name;
                this.price = price;
            }
        }
        
        // Attempt deserialization
        try (ObjectInputStream ois = new ObjectInputStream(
                new FileInputStream("product.ser"))) {
            Product deserialized = (Product) ois.readObject();
            System.out.println("Deserialized: " + deserialized.name);
        } catch (IOException | ClassNotFoundException e) {
            e.printStackTrace();  // Will throw InvalidClassException
        }
    }
}

此示例演示了由于缺少 serialVersionUID 导致的 InvalidClassException。 在序列化原始 Product 之后,我们通过添加一个字段修改了该类。 反序列化失败,因为生成的 UID 不匹配。 始终为可序列化类声明 serialVersionUID。

不兼容的 serialVersionUID

显式的 serialVersionUID 值必须在序列化和反序列化之间匹配。 手动更改此值会导致 InvalidClassException。 此示例显示了 UID 不匹配的场景。

Main.java
import java.io.*;

class Employee implements Serializable {
    private static final long serialVersionUID = 1L;  // Original UID
    String name;
    String department;
    
    public Employee(String name, String department) {
        this.name = name;
        this.department = department;
    }
}

public class Main {
    public static void main(String[] args) {
        // Serialize with original UID
        Employee emp = new Employee("Alice", "Engineering");
        try (ObjectOutputStream oos = new ObjectOutputStream(
                new FileOutputStream("employee.ser"))) {
            oos.writeObject(emp);
        } catch (IOException e) {
            e.printStackTrace();
        }
        
        // Simulate UID change
        class Employee implements Serializable {
            private static final long serialVersionUID = 2L;  // Changed UID
            String name;
            String department;
            
            public Employee(String name, String department) {
                this.name = name;
                this.department = department;
            }
        }
        
        // Attempt deserialization
        try (ObjectInputStream ois = new ObjectInputStream(
                new FileInputStream("employee.ser"))) {
            Employee deserialized = (Employee) ois.readObject();
            System.out.println("Deserialized: " + deserialized.name);
        } catch (IOException | ClassNotFoundException e) {
            e.printStackTrace();  // InvalidClassException: incompatible UIDs
        }
    }
}

此示例显示了由更改 serialVersionUID 引起的 InvalidClassException。 序列化数据具有 UID=1,但该类现在期望 UID=2。 异常消息将指示 UID 不匹配。 除非您有意进行不兼容的更改,否则切勿更改 serialVersionUID。

类结构更改

即使 serialVersionUID 匹配,某些类结构更改也是不兼容的。 更改字段类型或删除字段会导致 InvalidClassException。 此示例演示了这种不兼容的更改。

Main.java
import java.io.*;

class Account implements Serializable {
    private static final long serialVersionUID = 1L;
    String accountNumber;
    double balance;  // Original type: double
    
    public Account(String accountNumber, double balance) {
        this.accountNumber = accountNumber;
        this.balance = balance;
    }
}

public class Main {
    public static void main(String[] args) {
        // Serialize original version
        Account acc = new Account("123456", 1000.0);
        try (ObjectOutputStream oos = new ObjectOutputStream(
                new FileInputStream("account.ser"))) {
            oos.writeObject(acc);
        } catch (IOException e) {
            e.printStackTrace();
        }
        
        // Simulate incompatible change: change balance type to String
        class Account implements Serializable {
            private static final long serialVersionUID = 1L;  // Same UID
            String accountNumber;
            String balance;  // Changed type
            
            public Account(String accountNumber, String balance) {
                this.accountNumber = accountNumber;
                this.balance = balance;
            }
        }
        
        // Attempt deserialization
        try (ObjectInputStream ois = new ObjectInputStream(
                new FileInputStream("account.ser"))) {
            Account deserialized = (Account) ois.readObject();
            System.out.println("Deserialized: " + deserialized.accountNumber);
        } catch (IOException | ClassNotFoundException e) {
            e.printStackTrace();  // InvalidClassException: incompatible types
        }
    }
}

此示例显示了由于不兼容的字段类型更改导致的 InvalidClassException。 即使 serialVersionUID 匹配,将 balance 从 double 更改为 String 也会破坏反序列化。 序列化系统无法在这些类型之间自动转换。 这种更改需要自定义的 readObject/writeObject 方法。

缺少类定义

当反序列化期间类定义不可用时,会发生 InvalidClassException。 当该类在序列化期间可用但在反序列化期间丢失时,就会发生这种情况。 该示例模拟了此场景。

Main.java
import java.io.*;

// Original class that will be serialized
class Customer implements Serializable {
    private static final long serialVersionUID = 1L;
    String name;
    String email;
    
    public Customer(String name, String email) {
        this.name = name;
        this.email = email;
    }
}

public class Main {
    public static void main(String[] args) {
        // Serialize Customer object
        Customer cust = new Customer("Bob", "bob@example.com");
        try (ObjectOutputStream oos = new ObjectOutputStream(
                new FileOutputStream("customer.ser"))) {
            oos.writeObject(cust);
            System.out.println("Serialization complete");
        } catch (IOException e) {
            e.printStackTrace();
        }
        
        // Simulate missing class by not having Customer class definition
        // during deserialization
        
        // Attempt deserialization without Customer class
        try (ObjectInputStream ois = new ObjectInputStream(
                new FileInputStream("customer.ser"))) {
            Object deserialized = ois.readObject();
            System.out.println("Deserialized: " + deserialized.getClass());
        } catch (IOException | ClassNotFoundException e) {
            e.printStackTrace();  // InvalidClassException: class not found
        }
    }
}

此示例演示了由于缺少类定义导致的 InvalidClassException。 Customer 类在序列化期间可用,但在反序列化期间不可用。 异常消息将指示缺少的类名。 确保在反序列化期间类定义在类路径中可用。

自定义 serialPersistentFields

如果使用 serialPersistentFields 来控制序列化,如果不小心处理,可能会导致 InvalidClassException。 此示例显示了不正确的用法,其中声明的字段与实际的类字段不匹配。

Main.java
import java.io.*;
import java.io.ObjectStreamField;

class Settings implements Serializable {
    private static final long serialVersionUID = 1L;
    
    // Incorrect serialPersistentFields - missing 'darkMode' field
    private static final ObjectStreamField[] serialPersistentFields = {
        new ObjectStreamField("language", String.class)
    };
    
    String language;
    boolean darkMode;
    
    public Settings(String language, boolean darkMode) {
        this.language = language;
        this.darkMode = darkMode;
    }
}

public class Main {
    public static void main(String[] args) {
        // Serialize
        Settings settings = new Settings("en", true);
        try (ObjectOutputStream oos = new ObjectOutputStream(
                new FileOutputStream("settings.ser"))) {
            oos.writeObject(settings);
        } catch (IOException e) {
            e.printStackTrace();
        }
        
        // Attempt deserialization
        try (ObjectInputStream ois = new ObjectInputStream(
                new FileInputStream("settings.ser"))) {
            Settings deserialized = (Settings) ois.readObject();
            System.out.println("Language: " + deserialized.language);
        } catch (IOException | ClassNotFoundException e) {
            e.printStackTrace();  // InvalidClassException: field mismatch
        }
    }
}

此示例显示了由不正确的 serialPersistentFields 引起的 InvalidClassException。 该数组仅声明了 'language' 字段,而该类还具有 'darkMode' 字段。 序列化系统检测到此不匹配。 使用 serialPersistentFields 时,请确保所有可序列化字段都已正确声明。

来源

Java InvalidClassException 类文档

在本文中,我们介绍了在 Java 序列化期间导致 InvalidClassException 的常见场景。 了解这些情况有助于防止和解决 Java 应用程序中的序列化问题。

作者

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

列出所有Java教程