分析轮子(四)- 我也玩一把 Serializable.java 分析轮子(一)-ArrayList.java IDEA使用笔记(八)——自动生成 serialVersionUID 的设置

前言:在写 分析轮子(一)-ArrayList.java 的时候曾经下过一个结论 “实现Serializable接口,表示ArrayList是可序列化的”,这个结论是以往学习的经验所得,并且平时在编程的时候也遇到过其他的问题,比如:在写 IDEA使用笔记(八)——自动生成 serialVersionUID 的设置 的时候,其实就遇到了一个对象序列化和反序列化相关的问题,后来解决了,不过没有深入下去和总结一下。编程这件事情,最好实验一把,就算是他人已经研究明白的东西,自己如果不动手试试,可能印象总不是特别的深刻,哪怕随便玩一下,也许都会有完全不同的收获。

注:玩的是JDK1.7版 纸上得来终觉浅,绝知此事要躬行。(自勉之。。。

一:如果不实现 Serializable.java 接口,我要序列化对象会怎样呢?

1)来个简单的Bean,故意不实现 Serializable.java接口,如下所示

/**
 * @description:人类
 * @author:godtrue
 * @create:2018-09-09
 */
public class Person {//注意这里哈!
    /**
     * 身份证号
     */
    private int id;

    /**
     * 姓名
     */
    private String name;

    /**
     * 性别
     */
    private boolean sex;

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public boolean isSex() {
        return sex;
    }

    public void setSex(boolean sex) {
        this.sex = sex;
    }

    @Override
    public String toString() {
        final StringBuilder sb = new StringBuilder("Person{");
        sb.append("id=").append(id);
        sb.append(", name='").append(name).append('\'');
        sb.append(", sex=").append(sex);
        sb.append('}');
        return sb.toString();
    }
}

2)来个序列化和反序列化的测试类,跑一下,看看情况如何?

/**
 * @description:序列化和反序列化测试类
 * @author:godtrue
 * @create:2018-09-09
 */
public class SerializeAndDeserialize{
    /**
    *
    *@description: 测试入口,主方法
    *@param args
    *@return: void
    *@author: godtrue
    *@createTime: 2018-09-09
    *@version: v1.0
    */
    public static void main(String[] args){
        Person person = new Person();
        person.setId(1111);
        person.setName("双十一");
        person.setSex(true);

        try {
            serializePerson(person);
            Person personTemp = deserializePerson();
            System.out.println(personTemp);
        }catch (Exception e){
            e.printStackTrace();
        }

    }

    /**
    *
    *@description: 序列化人类对象方法
    *@param person
    *@return: void
    *@author: godtrue
    *@createTime: 2018-09-09
    *@version: v1.0
    */
    private static void serializePerson(Person person) throws IOException{
        ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("D://PersonInfo.text"));
        objectOutputStream.writeObject(person);
        System.out.println("serialize person success");
        objectOutputStream.close();
    }

    /**
    *
    *@description: 反序列化人类方法
    *@param
    *@return: com.godtrue.Person
    *@author: gotrue
    *@createTime: 2018-09-09
    *@version: v1.0
    */
    private static Person deserializePerson() throws ClassNotFoundException,IOException{
        ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream("D://PersonInfo.text"));
        Person person = (Person) objectInputStream.readObject();
        System.out.println("deserialize person success");
        return person;
    }
}

3)不实现 Serializable.java 接口,进行对象序列化的后果很严重(序列化不成),并且抛出 java.io.NotSerializableException 异常

Connected to the target VM, address: '127.0.0.1:53429', transport: 'socket'
java.io.NotSerializableException: com.godtrue.Person
    at java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1183)
    at java.io.ObjectOutputStream.writeObject(ObjectOutputStream.java:347)
    at com.godtrue.SerializeAndDeserialize.serializePerson(SerializeAndDeserialize.java:28)
    at com.godtrue.SerializeAndDeserialize.main(SerializeAndDeserialize.java:18)
Disconnected from the target VM, address: '127.0.0.1:53429', transport: 'socket'

Process finished with exit code 0

4)跟一下,看看这个异常时哪里抛出来的

    /**
     * Write the specified object to the ObjectOutputStream.  The class of the
     * object, the signature of the class, and the values of the non-transient
     * and non-static fields of the class and all of its supertypes are
     * written.  Default serialization for a class can be overridden using the
     * writeObject and the readObject methods.  Objects referenced by this
     * object are written transitively so that a complete equivalent graph of
     * objects can be reconstructed by an ObjectInputStream.
     *
     * <p>Exceptions are thrown for problems with the OutputStream and for
     * classes that should not be serialized.  All exceptions are fatal to the
     * OutputStream, which is left in an indeterminate state, and it is up to
     * the caller to ignore or recover the stream state.
     *
     * @throws  InvalidClassException Something is wrong with a class used by
     *          serialization.
     * @throws  NotSerializableException Some object to be serialized does not
     *          implement the java.io.Serializable interface.
     * @throws  IOException Any exception thrown by the underlying
     *          OutputStream.
     */
    public final void writeObject(Object obj) throws IOException {
        if (enableOverride) {
            writeObjectOverride(obj);
            return;
        }
        try {
            writeObject0(obj, false);//往这里走啦!
        } catch (IOException ex) {
            if (depth == 0) {
                writeFatalException(ex);
            }
            throw ex;
        }
    }
/**
     * Underlying writeObject/writeUnshared implementation.
     */
    private void writeObject0(Object obj, boolean unshared)
        throws IOException
    {
        boolean oldMode = bout.setBlockDataMode(false);
        depth++;
        try {
            // handle previously written and non-replaceable objects
            int h;
            if ((obj = subs.lookup(obj)) == null) {
                writeNull();
                return;
            } else if (!unshared && (h = handles.lookup(obj)) != -1) {
                writeHandle(h);
                return;
            } else if (obj instanceof Class) {
                writeClass((Class) obj, unshared);
                return;
            } else if (obj instanceof ObjectStreamClass) {
                writeClassDesc((ObjectStreamClass) obj, unshared);
                return;
            }

            // check for replacement object
            Object orig = obj;
            Class cl = obj.getClass();
            ObjectStreamClass desc;
            for (;;) {
                // REMIND: skip this check for strings/arrays?
                Class repCl;
                desc = ObjectStreamClass.lookup(cl, true);
                if (!desc.hasWriteReplaceMethod() ||
                    (obj = desc.invokeWriteReplace(obj)) == null ||
                    (repCl = obj.getClass()) == cl)
                {
                    break;
                }
                cl = repCl;
            }
            if (enableReplace) {
                Object rep = replaceObject(obj);
                if (rep != obj && rep != null) {
                    cl = rep.getClass();
                    desc = ObjectStreamClass.lookup(cl, true);
                }
                obj = rep;
            }

            // if object replaced, run through original checks a second time
            if (obj != orig) {
                subs.assign(orig, obj);
                if (obj == null) {
                    writeNull();
                    return;
                } else if (!unshared && (h = handles.lookup(obj)) != -1) {
                    writeHandle(h);
                    return;
                } else if (obj instanceof Class) {
                    writeClass((Class) obj, unshared);
                    return;
                } else if (obj instanceof ObjectStreamClass) {
                    writeClassDesc((ObjectStreamClass) obj, unshared);
                    return;
                }
            }

            // 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());//最终在这里抛出了对应的异常
                }
            }
        } finally {
            depth--;
            bout.setBlockDataMode(oldMode);
        }
    }

二:嗯,那好吧!现在终于明白如果不实现 Serializable.java 接口,序列化对象的后果了,那就实现一下吧!毕竟JAVA平台都这么规定了,除非换个平台了!

1)调整 Person.java 类,使其实现 Serializable.java 接口,如下所示

/**
 * @description:人类
 * @author:godtrue
 * @create:2018-09-09
 */
public class Person implements Serializable{//注意这里啦!
    /**
     * 身份证号
     */
    private int id;

    /**
     * 姓名
     */
    private String name;

    /**
     * 性别
     */
    private boolean sex;

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public boolean isSex() {
        return sex;
    }

    public void setSex(boolean sex) {
        this.sex = sex;
    }

    @Override
    public String toString() {
        final StringBuilder sb = new StringBuilder("Person{");
        sb.append("id=").append(id);
        sb.append(", name='").append(name).append('\'');
        sb.append(", sex=").append(sex);
        sb.append('}');
        return sb.toString();
    }
}

2)序列化和反序列化的测试类 原封未动,这里就不贴出来了

3)Person.java 实现 Serializable.java 后,对象的序列化和反序列化都成功了,如下所示

serialize person success
deserialize person success
Person{id=1111, name='双十一', sex=true}

Process finished with exit code 0

4)序列化到文件中的信息,如下所示

三:serialVersionUID 的作用

实验步骤:

1)先将 Person.java 类的对象序列化到 D://PersonInfo.text 文件中(在Person.java类中没有显示声明 serialVersionUID)

2)为Person.java 类添加一个年龄属性

    /**
     * 年龄
     */
    private int age;

3)反序列化 D://PersonInfo.text 文件中对象信息

4)执行结果如下(抛出了 java.io.InvalidClassException ):

Connected to the target VM, address: '127.0.0.1:51721', transport: 'socket'
Disconnected from the target VM, address: '127.0.0.1:51721', transport: 'socket'
java.io.InvalidClassException: com.godtrue.Person; local class incompatible: stream classdesc serialVersionUID = 2168487965208983906, local class serialVersionUID = 7553450974679663255
    at java.io.ObjectStreamClass.initNonProxy(ObjectStreamClass.java:617)
    at java.io.ObjectInputStream.readNonProxyDesc(ObjectInputStream.java:1622)
    at java.io.ObjectInputStream.readClassDesc(ObjectInputStream.java:1517)
    at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:1771)
    at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1350)
    at java.io.ObjectInputStream.readObject(ObjectInputStream.java:370)
    at com.godtrue.SerializeAndDeserialize.deserializePerson(SerializeAndDeserialize.java:63)
    at com.godtrue.SerializeAndDeserialize.main(SerializeAndDeserialize.java:28)

Process finished with exit code 0

5)分析——看抛错的日志信息,引起这个问题的原因是因为,序列化和反序列化信息中的 serialVersionUID 这个属性的值不一致造成的,那问题来了 serialVersionUID 这个属性哪里来的?这个属性的值又是哪里来的?为什么同一个对象的序列化和反序列化信息中的有些信息不一样呢?

5-1)serialVersionUID 这个属性是JDK工具添加上去
5-2)serialVersionUID 这个属性的值,如果没有显式指明,则会由JDK工具自动生成,如果显式声明了,则使用显式声明的
5-3)serialVersionUID 这个属性的默认生成规则,和类中的信息有关,如果类有所改变,则会影响此属性的值的生成

6)继续试验下,5)中的结论,试验步骤如下:
6-1)先将Person.java中的 age 属性去掉,然后显式的声明 private static final long serialVersionUID=1L;
6-2)然后,将 Person.java 类对应的对象信息,序列化到 D://PersonInfo.text 文件中
6-3)然后,将 Person.java 类的 age 属性再添加回去
6-4)然后,将Person.java 类的对象信息反序列化为对象,
6-5)试验ok了,序列化和反序列化都没有问题,只是反序列化后的对象信息中 age 属性的值是默认属性 0

7)继续试验,逆向的验证一下反序列化,试验步骤如下:
7-1)先将Person.java中的 age 属性添加上,然后显式的声明 private static final long serialVersionUID=1L;
7-2)然后,将 Person.java 类对应的对象信息,序列化到 D://PersonInfo.text 文件中
7-3)然后,将 Person.java 类的 age 属性再去掉
7-4)然后,将Person.java 类的对象信息反序列化为对象,
7-5)试验ok了,序列化和反序列化都没有问题,只是,反序列化后的对象信息中没有 age 属性而已,(去掉后,反序列化当然是没有的)

8)从上述的试验中,我们可以发现一些有趣的事情
8-1)当
serialVersionUID 属性的值,不一致时,即使是同一个类,他的序列化和反序列化前后的属性没有增减,也是不能正确反序列化的
8-2)当 serialVersionUID 属性的值,一致时,同一个类,他的序列化和反序列化前后的属性有增减,也能正确反序列化的

参考:
http://www.oracle.com/technetwork/articles/java/javaserial-1536170.html
https://www.ibm.com/developerworks/cn/java/j-lo-serial/index.html
https://www.cnblogs.com/xdp-gacl/p/3777987.html

http://ju.outofmemory.cn/entry/298220
https://blog.csdn.net/qq_27093465/article/details/78544505

https://blog.csdn.net/jason_279/article/details/52947093
https://blog.csdn.net/u011784767/article/details/78156319?locationNum=2&fps=1
https://www.cnblogs.com/wangg-mail/p/4354709.html
https://blog.csdn.net/leixingbang1989/article/details/50556966

https://www.cnblogs.com/DSNFZ/articles/7618470.html
http://www.cnblogs.com/huhx/p/serializable.html
https://blog.csdn.net/so_geili/article/details/78931742

https://www.oschina.net/question/4873_23270
https://www.cnblogs.com/qq3111901846/p/7894532.html
https://www.cnblogs.com/gtaxmjld/p/4866931.html
https://blog.csdn.net/zhangliao613/article/details/51086562

猜你喜欢

转载自www.cnblogs.com/godtrue/p/9615155.html