杰瑞科技汇

Serializable Java如何实现对象持久化?

Of course! Let's break down Serializable in Java in a comprehensive way, from the basic concept to practical examples and best practices.

Serializable Java如何实现对象持久化?-图1
(图片来源网络,侵删)

What is Serializable?

At its core, Serializable is a marker interface in Java. A marker interface is an interface with no methods or fields. Its sole purpose is to signal to the Java Virtual Machine (JVM) that objects of a class implementing this interface have a special capability.

In this case, the capability is serialization.

Serialization is the process of converting an object's state (its data, or fields) into a byte stream. This byte stream can then be:

  • Saved to a file.
  • Sent over a network to another machine.
  • Stored in a database.

The reverse process, converting the byte stream back into a Java object, is called deserialization.

Serializable Java如何实现对象持久化?-图2
(图片来源网络,侵删)

Why is it Used?

Serializable is used for two primary purposes:

  1. Persistence: To save an object's state to a file or database so it can be recreated later, even after the program has terminated. This is how you can "save a game" or remember a user's settings.
  2. Networking (RMI & Sockets): To send objects from one Java Virtual Machine (JVM) to another. This is fundamental for technologies like Remote Method Invocation (RMI) and is used extensively in web applications for sending data between a client and a server (e.g., sending a User object as JSON, which is a text-based serialization format).

How to Use Serializable (The Basic Syntax)

Making a class serializable is incredibly simple. You just need to implement the java.io.Serializable interface.

Let's take a simple User class.

import java.io.Serializable;
// 1. Implement the Serializable interface
public class User implements Serializable {
    // 2. It's highly recommended to declare a version ID
    private static final long serialVersionUID = 1L;
    private String username;
    private transient String password; // We'll discuss 'transient' later
    private int age;
    // Constructor
    public User(String username, String password, int age) {
        this.username = username;
        this.password = password;
        this.age = age;
    }
    // Getters and toString() for easy printing
    public String getUsername() {
        return username;
    }
    @Override
    public String toString() {
        return "User{" +
                "username='" + username + '\'' +
                ", password='" + password + '\'' +
                ", age=" + age +
                '}';
    }
}

Now, let's create a program to serialize (write) this User object to a file and then deserialize (read) it back.

Serializable Java如何实现对象持久化?-图3
(图片来源网络,侵删)
import java.io.*;
public class SerializationDemo {
    public static void main(String[] args) {
        // Create a User object
        User userToSerialize = new User("john_doe", "secret123", 30);
        // 1. Serialize the object to a file
        try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("user.ser"))) {
            oos.writeObject(userToSerialize);
            System.out.println("Object serialized successfully to user.ser");
        } catch (IOException e) {
            e.printStackTrace();
        }
        // 2. Deserialize the object from the file
        User deserializedUser = null;
        try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream("user.ser"))) {
            // The readObject() method returns an Object, so we must cast it
            deserializedUser = (User) ois.readObject();
            System.out.println("Object deserialized successfully from user.ser");
        } catch (IOException | ClassNotFoundException e) {
            e.printStackTrace();
        }
        // Print the deserialized object to verify
        if (deserializedUser != null) {
            System.out.println("Deserialized User: " + deserializedUser);
        }
    }
}

After running this, you will have a file named user.ser in your project directory. If you open it, it will look like gibberish because it's a binary format, not text.


Key Concepts and Best Practices

The serialVersionUID

This is the most important concept to understand about Serializable.

  • What is it? It's a unique identifier for a serializable class. It's a private static final long field.
  • Why is it needed? The JVM uses this ID to ensure that the object being deserialized is compatible with the class definition currently in memory.
  • What happens if it's missing? The JVM automatically calculates a hash-like value based on the class's name, interfaces, fields, methods, etc. This is called a "default serialVersionUID". This is dangerous! If you make even a small change to the class (like adding a new field), the default serialVersionUID will change. When you try to deserialize an old object, the JVM will see a mismatch and throw an InvalidClassException.
  • Best Practice: Always explicitly declare a serialVersionUID. If you do this, you can add new fields to your class without breaking backward compatibility (the deserialization will just set the new fields to their default values, like null or 0).
private static final long serialVersionUID = 123456789L; // Choose a unique number

The transient Keyword

Not all data should be serialized. For example, you might not want to save a user's password in plain text, or a field that represents a temporary resource like a network connection.

The transient keyword marks a field so that the JVM ignores it during serialization. Its value will not be saved to the byte stream.

When the object is deserialized, any field marked as transient will be restored to its default value:

  • Object references -> null
  • int -> 0
  • boolean -> false
  • etc.
public class User implements Serializable {
    // ...
    private transient String password; // This will NOT be serialized
    // ...
}

In our demo, if you deserialize the user.ser file, the password field will be null.

Customizing Serialization with writeObject and readObject

Sometimes the default serialization isn't enough. You might need to:

  • Encrypt sensitive data before writing it.
  • Calculate a checksum or hash to ensure data integrity.
  • Reconstruct non-serializable objects (like File handles) during deserialization.

You can achieve this by providing private methods named writeObject and readObject in your class.

import java.io.*;
public class User implements Serializable {
    // ... (fields and serialVersionUID)
    private void writeObject(ObjectOutputStream oos) throws IOException {
        // This is called by the default writeObject()
        // We can customize the writing process here.
        System.out.println("Custom writeObject called.");
        // 1. Perform custom logic (e.g., encrypt the password)
        String encryptedPassword = "ENCRYPTED_" + password; // Simplified example
        // 2. Call the default writeObject to write the rest of the object
        oos.defaultWriteObject();
        // 3. Now, write our custom data separately
        oos.writeObject(encryptedPassword);
    }
    private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException {
        // This is called by the default readObject()
        System.out.println("Custom readObject called.");
        // 1. Call the default readObject to read the non-transient fields
        ois.defaultReadObject();
        // 2. Read our custom data that we wrote separately
        String encryptedPassword = (String) ois.readObject();
        // 3. Perform custom logic (e.g., decrypt the password)
        this.password = encryptedPassword.replace("ENCRYPTED_", "");
    }
}

Security Considerations

Serialization is powerful but can be a security risk if not handled carefully.

  1. Deserialization of Untrusted Data: This is a major vulnerability. If an attacker can make your application deserialize a maliciously crafted byte stream, they can execute arbitrary code. This is known as "Deserialization Vulnerability" or "Java Object Deserialization Vulnerability".
  2. Storing Sensitive Data: Never serialize sensitive information like passwords, API keys, or credit card numbers without encryption. Use the transient keyword or custom serialization to protect this data.

Modern Alternatives

While Serializable is built into Java, it's often considered "heavyweight" and has some drawbacks:

  • It's tied to Java. You can't easily send a Serializable object to a non-Java application (like a Python or Node.js server).
  • The binary format is not human-readable.
  • It can be slow and inefficient for large object graphs.

For these reasons, modern applications often prefer other serialization formats:

Format Type Use Case Libraries
JSON Text-based Web APIs (client-server communication), configuration files org.json, Jackson, Gson, Moshi
XML Text-based Legacy systems, configuration files, web services JAXB, XStream
Protocol Buffers (Protobuf) Binary High-performance microservices, RPC Google's Protobuf, gRPC
Avro Binary Big data systems (Hadoop, Spark), schema evolution Apache Avro

These formats are generally more flexible, language-agnostic, and often more performant.

Summary

Feature Description
What it is A marker interface (java.io.Serializable) that enables an object to be converted to a byte stream (serialization) and back (deserialization).
How to use public class MyClass implements Serializable { ... }
serialVersionUID A mandatory private static final long field for version control. Prevents InvalidClassException when the class evolves.
transient A keyword that marks a field to be skipped during serialization.
Custom Logic Implement private writeObject() and private readObject() for fine-grained control over the process.
Security Be wary of deserializing untrusted data, as it can lead to remote code execution.
Alternatives For cross-platform communication, prefer text-based formats like JSON or high-performance binary formats like Protocol Buffers.
分享:
扫描分享到社交APP
上一篇
下一篇