Java 中的序列化 Serializable 和 Externalizable

Java 面试 序列化 大约 11131 字

Serializable

static修饰的字段是不会被序列化的。

transient修饰符修饰的字段也是不会被序列化的。

public class Person implements Serializable {

    private static final long serialVersionUID = -8483382254070647732L;

    private String name;

    private Integer age;

    private transient String[] hobby;

    // set() get() 省略

    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", hobby=" + Arrays.toString(hobby) +
                '}';
    }

    private void readObject(ObjectInputStream objectInputStream) throws IOException, ClassNotFoundException {
        // 调用默认的反序列化函数
        objectInputStream.defaultReadObject();

        // 手工检查反序列化后年龄
        if (age < 0 || age > 150) {
            throw new IllegalArgumentException("age 非法: " + age);
        }

    }
}

序列化到文件

public static void serialize() throws IOException {
    Person person = new Person();
    person.setName("Alice");
//    person.setAge(180);
    person.setAge(18);
    String[] hobby = {"basketball"};
    person.setHobby(hobby);

    ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream(new File("person.txt")));
    objectOutputStream.writeObject(person);
    objectOutputStream.close();
    System.out.println("serialize complete!");
}

反序列化

public static void deserialize() throws IOException, ClassNotFoundException {
    ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream(new File("person.txt")));
    Person person = (Person) objectInputStream.readObject();
    System.out.println(person);
}

NotSerializableException

如果Person不实现Serializable接口,则序列化时会抛出NotSerializableException异常:

Exception in thread "main" java.io.NotSerializableException: Person
    at java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1184)
    at java.io.ObjectOutputStream.writeObject(ObjectOutputStream.java:348)
    at SerializableDemo.serialize(SerializableDemo.java:26)
    at SerializableDemo.main(SerializableDemo.java:10)

ObjectOutputStreamwriteObject0()方法判断是StringArrayEnum或者实现了Serializable接口的对象可以序列化,其余的将抛出NotSerializableException异常。

public class ObjectInputStream extends InputStream implements ObjectInput, ObjectStreamConstants {
    private final boolean enableOverride;

    public ObjectInputStream(InputStream in) throws IOException {
        // 省略代码
        enableOverride = false;
    }

    public final void writeObject(Object obj) throws IOException {
        if (enableOverride) {
            writeObjectOverride(obj);
            return;
        }
        try {
            writeObject0(obj, false);
        } catch (IOException ex) {
            // 省略代码
        }
    }

    private void writeObject0(Object obj, boolean unshared) throws IOException {
        ...

            // remaining cases
            if (obj instanceof String) {
                writeString((String) obj, unshared);
            } else if (cl.isArray()) {
                writeArray(obj, desc, unshared);
            } else if (obj instanceof Enum) {
                writeEnum((Enum<?>) obj, desc, unshared);
            } else if (obj instanceof Serializable) {
                writeOrdinaryObject(obj, desc, unshared);
            } else {
                if (extendedDebugInfo) {
                    throw new NotSerializableException(
                        cl.getName() + "\n" + debugInfoStack.toString());
                } else {
                    throw new NotSerializableException(cl.getName());
                }
            }
        ...
    }
}

serialVersionUID

serialVersionUID是序列化前后的唯一标识符,如果没有人为显式定义过serialVersionUID,那编译器会为它自动声明一个。

修改了类的字段(未显示指定serialVersionUID前提下)或修改了serialVersionUID后,反序列化会抛出InvalidClassException异常。

Exception in thread "main" java.io.InvalidClassException: Person; local class incompatible: stream classdesc serialVersionUID = 3625195237367014680, local class serialVersionUID = -8615747153399946143
    at java.io.ObjectStreamClass.initNonProxy(ObjectStreamClass.java:616)
    at java.io.ObjectInputStream.readNonProxyDesc(ObjectInputStream.java:1843)
    at java.io.ObjectInputStream.readClassDesc(ObjectInputStream.java:1713)
    at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:2000)
    at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1535)
    at java.io.ObjectInputStream.readObject(ObjectInputStream.java:422)
    at SerializableDemo.deserialize(SerializableDemo.java:15)
    at SerializableDemo.main(SerializableDemo.java:10)

readObject

反序列化时做一个校验等逻辑。反序列化时自动调用实体类中定义的readObject(ObjectInputStream ois)方法。

private void readObject(ObjectInputStream objectInputStream) throws IOException, ClassNotFoundException {
    // 调用默认的反序列化函数
    objectInputStream.defaultReadObject();

    // 手工检查反序列化后年龄
    if (age < 0 || age > 150) {
        throw new IllegalArgumentException("age 非法: " + age);
    }

}

底层原理:ObjectStreamClass调用类中的readObject方法。

public class ObjectStreamClass implements Serializable {

    private ObjectStreamClass(final Class<?> cl) {
        this.cl = cl;
        serializable = Serializable.class.isAssignableFrom(cl);
        externalizable = Externalizable.class.isAssignableFrom(cl);

        if (serializable) {
            AccessController.doPrivileged(new PrivilegedAction<Void>() {
                public Void run() {
                    ...

                    if (externalizable) {
                        cons = getExternalizableConstructor(cl);
                    } else {
                        cons = getSerializableConstructor(cl);
                        writeObjectMethod = getPrivateMethod(cl, "writeObject",
                            new Class<?>[] { ObjectOutputStream.class },
                            Void.TYPE);
                        readObjectMethod = getPrivateMethod(cl, "readObject",
                            new Class<?>[] { ObjectInputStream.class },
                            Void.TYPE);
                        readObjectNoDataMethod = getPrivateMethod(
                            cl, "readObjectNoData", null, Void.TYPE);
                        hasWriteObjectData = (writeObjectMethod != null);
                    }
                    writeReplaceMethod = getInheritableMethod(
                        cl, "writeReplace", null, Object.class);
                    readResolveMethod = getInheritableMethod(
                        cl, "readResolve", null, Object.class);
                    return null;
                }
            });
        } else {
            suid = Long.valueOf(0);
            fields = NO_FIELDS;
        }

        ...
    }
}

可序列化的单例类有可能并不单例

public class Singleton implements Serializable {

    private static final long serialVersionUID = -1576643344804979563L;

    private Singleton() {
    }

    private static class SingletonHolder {
        private static final Singleton singleton = new Singleton();
    }

    public static synchronized Singleton getSingleton() {
        return SingletonHolder.singleton;
    }
}

测试:返回false,两个对象不相等。

    public static void main(String[] args) throws IOException, ClassNotFoundException {
        ObjectOutputStream objectOutputStream =
                new ObjectOutputStream(
                        new FileOutputStream( new File("singleton.txt") )
                );
        // 将单例对象先序列化到文本文件singleton.txt中
        objectOutputStream.writeObject( Singleton.getSingleton() );
        objectOutputStream.close();

        ObjectInputStream objectInputStream =
                new ObjectInputStream(
                        new FileInputStream( new File("singleton.txt") )
                );
        // 将文本文件singleton.txt中的对象反序列化为singleton1
        Singleton singleton1 = (Singleton) objectInputStream.readObject();
        objectInputStream.close();

        Singleton singleton2 = Singleton.getSingleton();

        // 运行结果竟打印 false !
        System.out.println( singleton1 == singleton2 );
    }

解决方法:在单例类中手写readResolve()函数,直接返回单例对象,来规避。

private Object readResolve() {
    return SingletonHolder.singleton;
}

Externalizable

可指定字段序列化与反序列化,必须实现writeExternal()readExternal()方法,transient不再起作用。

实体类

public class Animal implements Externalizable {

    private static final long serialVersionUID = -4504729913144437628L;

    // 复写了 writeExternal 和 readExternal 后,transient 不起作用
    private transient String name;

    private Integer age;

    private String[] hobby;

    // 省略 get set

    @Override
    public String toString() {
        return "Animal{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", hobby=" + Arrays.toString(hobby) +
                '}';
    }

    @Override
    public void writeExternal(ObjectOutput out) throws IOException {
        out.writeObject(name);
        out.writeObject(age);
    }

    @Override
    public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
        name = (String) in.readObject();
        age = (Integer) in.readObject();
    }
}

测试类

public class ExternalizableDemo {

    public static void main(String[] args) throws IOException, ClassNotFoundException {
        serialize();
        deserialize();
    }

    private static void deserialize() throws IOException, ClassNotFoundException {
        ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream(new File("animal.txt")));
        Animal animal = (Animal) objectInputStream.readObject();
        System.out.println(animal);
    }

    private static void serialize() throws IOException {
        Animal animal = new Animal();
        animal.setName("小兔子");
        String[] hobby = {"eat"};
        animal.setHobby(hobby);
        animal.setAge(3);
        ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream(new File("animal.txt")));
        objectOutputStream.writeObject(animal);
        objectOutputStream.close();
        System.out.println("serialize complete!");
    }

}

no valid constructor

Externalizable实现类必须提供无参构造函数,否则会抛出InvalidClassException异常,并提示no valid constructor

相关底层代码:

public class ObjectStreamClass implements Serializable {
    private Class<?> cl;

    private ObjectStreamClass(final Class<?> cl) {
        this.cl = cl;

        if (externalizable) {
            cons = getExternalizableConstructor(cl);
        } else {
            cons = getSerializableConstructor(cl);
        }

        if (isEnum) {
            deserializeEx = new ExceptionInfo(name, "enum type");
        } else if (cons == null) {
            deserializeEx = new ExceptionInfo(name, "no valid constructor");
        }
    }

    private static Constructor<?> getExternalizableConstructor(Class<?> cl) {
        try {
            Constructor<?> cons = cl.getDeclaredConstructor((Class<?>[]) null);
            cons.setAccessible(true);
            return ((cons.getModifiers() & Modifier.PUBLIC) != 0) ?
                cons : null;
        } catch (NoSuchMethodException ex) {
            return null;
        }
    }
}

参考

https://www.zhihu.com/question/26475281/answer/1257699781

阅读 502 · 发布于 2021-04-16

————        END        ————

扫描下方二维码关注公众号和小程序↓↓↓

扫描二维码关注我
昵称:
随便看看 换一批