已有Serializable的read/writeObject,为何还需Externalizable?
Great question—this is one of those Java serialization subtleties that confuses even seasoned developers. Let’s break down the core differences, why Externalizable exists, and when you’d choose it over the more common readObject/writeObject hooks.
First, the Core Difference in Control
When you implement Serializable and override readObject/writeObject, you’re augmenting the JVM’s default serialization process. The JVM still handles the heavy lifting: it writes class metadata, manages field visibility, and you can choose to call defaultReadObject()/defaultWriteObject() to let it handle standard fields before adding your custom logic.
Externalizable, on the other hand, is a total opt-out from the JVM’s default behavior. You’re 100% responsible for every part of serialization and deserialization: writing/reading every field, handling versioning, and even ensuring the object can be instantiated (more on that later). The JVM doesn’t do any automatic field serialization here—it’s all on you.
Why Does Externalizable Exist?
1. Extreme Performance Optimization
The JVM’s default serialization adds a lot of overhead: class names, field descriptors, and other metadata. For large objects, high-throughput systems (like distributed caches or message queues), or scenarios where every byte counts, this overhead can be a bottleneck.
With Externalizable, you can strip out all that metadata and serialize only the raw data you need. For example, instead of writing a field name and its value, you just write the value directly in a fixed order. This cuts down on both serialization time and the size of the serialized byte stream.
2. Full Control Over Serialization Logic
Sometimes you need to do things the default serialization system can’t handle cleanly:
- Serializing objects to a non-Java-compatible format (so a C++ or Python app can read the data)
- Applying custom encryption, compression, or encoding to fields before writing them
- Handling complex object state that doesn’t map neatly to standard fields (like transient state that needs to be reconstructed from other data)
readObject/writeObject let you add custom logic, but you’re still working within the JVM’s framework. Externalizable lets you build the entire process from scratch, no constraints.
3. Granular Version Compatibility
While serialVersionUID helps with basic versioning, it’s a blunt tool. If your class changes drastically (e.g., renaming fields, removing entire sections of state), Serializable’s default handling can break deserialization of old data.
With Externalizable, you can implement explicit version checks in readExternal. For example:
@Override public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException { int version = in.readInt(); // Write this first in writeExternal if (version == 1) { name = in.readUTF(); age = in.readInt(); } else if (version == 2) { name = in.readUTF(); age = in.readInt(); email = in.readUTF(); // New field in v2 } }
This level of control isn’t possible with Serializable’s default hooks.
When to Choose Externalizable Over readObject/writeObject?
- You need maximum performance: If serialization/deserialization is a hot path in your application, the overhead saved by
Externalizablecan be significant. - You need full format control: When you’re working with cross-platform systems, or need to enforce a strict, compact data format.
- You’re dealing with large or high-volume objects: For example, serializing millions of small objects—each one’s metadata overhead adds up quickly.
Important Caveat
Unlike Serializable, Externalizable requires your class to have a public no-arg constructor. The JVM uses this to instantiate the object before calling readExternal()—whereas Serializable reconstructs objects directly from the byte stream without invoking constructors. This is a critical detail that trips up many developers.
Quick Code Comparison
Serializable with Custom Hooks
public class User implements Serializable { private static final long serialVersionUID = 1L; private String name; private int age; private void writeObject(ObjectOutputStream out) throws IOException { out.defaultWriteObject(); // Add custom logic: write name in uppercase out.writeUTF(name.toUpperCase()); } private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException { in.defaultReadObject(); String upperName = in.readUTF(); // Use custom data as needed this.name = upperName.toLowerCase(); } }
Externalizable Implementation
public class User implements Externalizable { private String name; private int age; // Mandatory no-arg constructor public User() {} @Override public void writeExternal(ObjectOutput out) throws IOException { // No metadata, just raw data out.writeUTF(name); out.writeInt(age); } @Override public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException { // Must match writeExternal's order exactly name = in.readUTF(); age = in.readInt(); } }
Wrap-Up
Most of the time, Serializable with readObject/writeObject is sufficient—it’s easier to implement and less error-prone. But Externalizable fills a niche for cases where you need absolute control over the serialization process or can’t afford the overhead of the JVM’s default system.
内容的提问来源于stack exchange,提问作者RaT

