美文网首页Java
序列化机制—Serializable需要注意的几点问题

序列化机制—Serializable需要注意的几点问题

作者: 未见哥哥 | 来源:发表于2019-05-12 14:43 被阅读2次
    Serializable 需要注意的坑

    多引用写入

    //有 bug 版本
    //SerializableTest.java
    private static void serializeBug() throws Exception {
        System.out.println("序列化...");
        Student student = new Student("六号表哥", 25);
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(FILE_PATH));
        oos.writeObject(student);
        //修改属性再次写入
        student.setName("王武");
        oos.writeObject(student);
        oos.flush();
        oos.close();
    }
    
    //SerializableTest.java
    private static void deserialize() throws Exception {
        System.out.println("反序列化...");
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream(FILE_PATH));
        Student student = (Student) ois.readObject();
        Student student2 = (Student) ois.readObject();
        ois.close();
        System.out.println(student);
        System.out.println(student2);
    }
    

    测试输出结果

    从输出结果来看,name 属性并没有发生改变

    序列化...
    反序列化...
    Student{name='六号表哥', age=25',color = null}
    Student{name='六号表哥', age=25',color = null}
    

    使用以下两种方法来修复这个问题:

    //bugfix 版本
    private static void serializeBugFix() throws Exception {
        System.out.println("序列化...");
        Student student = new Student("六号表哥", 25);
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(FILE_PATH));
        oos.writeObject(student);
        //方式一
        oos.reset();
        student.setName("王武");
        oos.writeObject(student);
        //方式二
        //oos.writeUnshared(student);
        oos.close();
    }
    
    private static void deserialize() throws Exception {
        System.out.println("反序列化...");
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream(FILE_PATH));
        Student student = (Student) ois.readObject();
        Student student2 = (Student) ois.readObject();
        ois.close();
        System.out.println(student);
        System.out.println(student2);
    }
    

    输出结果:

    序列化...
    反序列化...
    Student{name='六号表哥', age=25',color = null}
    Student{name='王武', age=25',color = null}
    

    序列化的属性如果没有实现序列化接口会在序列化时会报错

    public class Student implements Serializable {
      private String name;
      private int age;
      /**
      * Color 类也是需要实现序列化接口的。
      */
      private Color color;//这里如果没有实现序列化接口,那么在 Student 对象序列化时将会报错
    }
    
    Exception in thread "main" java.io.NotSerializableException: com.example._super.Color
        at java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1184)
        at java.io.ObjectOutputStream.defaultWriteFields(ObjectOutputStream.java:1548)
        at java.io.ObjectOutputStream.writeSerialData(ObjectOutputStream.java:1509)
        at java.io.ObjectOutputStream.writeOrdinaryObject(ObjectOutputStream.java:1432)
        at java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1178)
        at java.io.ObjectOutputStream.writeObject(ObjectOutputStream.java:348)
        at com.example.seriable.SerializableTest.serialize(SerializableTest.java:43)
        at com.example.seriable.SerializableTest.main(SerializableTest.java:18)
    
    

    枚举序列化问题

    下图是 ObjectOutputStream 对枚举的序列化和反序列化的描述,大概的意思是,枚举的的序列化和反序列化和普通的对象不一样,它只会序列化枚举的 name 值,而反序列化是根据得到的 name 值去调用 enum 的 valueOf(name)得到枚举对象。还有一点不同的是枚举的序列化和反序列化过程是不能被定制的,因此类似于 readObject,writeObject,writeExternal 等方法是无效的,并且对应的 serialVersionUID 也被指定为0,设置为其他值也是无效的,这些内容都会被忽略掉。

    关于枚举序列化反序列化问题

    栗子一:下面通过一个栗子来验证一下:

    public enum Color /*implements Serializable*/ {//枚举本身就是实现了 Serializable 因此这里不需要去实现哦~
        RED, GREEN, BLUE;
        //枚举类的 serialVersionUID 会被 JVM 忽略
        private static final long serialVersionUID = 1L;
        public static void main(String[] args) {
    
            ObjectStreamClass desc = ObjectStreamClass.lookup(Color.RED.getClass());
            //枚举类的 serialVersionUID 都是 0
            //因此枚举类在增加新的枚举对象时并不会出现反序列化失败问题
            long serialVersionUID = desc.getSerialVersionUID();//0
            System.out.println(serialVersionUID);
        }
    }
    

    上面是用于验证枚举的 serialVersionUID 值的,从输出结果可以知道尽管 Color 类设置了 serialVersionUID = 1L,但是我们通过 ObjectStreamClass#getSerialVersionUID 得到的值仍然是0,有兴趣的读者可以验证一下。

    栗子二:验证枚举只序列化了 name 属性,而没有序列化 ordinal 属性

    枚举的序列化与反序列化

    从输出结果来看,序列化确实是没有序列化 orinal 属性,而只是序列化了 name 属性,而在反序列化时会根据 name 属性去调用 valueOf(name) 得到对应的枚举对象。

    public enum Color2 {
    
        //序列化前的排序:RED,GREEN, BLUE;
    //    RED,GREEN, BLUE;
        //序列化后的排序:GREEN, RED, BLUE;
        GREEN, RED, BLUE;
        private static final String PATH = "./color.bin";
    
        /**
         * 序列化前的结果
         * RED-0
         * GREEN-1
         * BLUE-2
         * ---------------------
         * 反序列化得到的输出结果
         * RED-1
         * GREEN-0
         * BLUE-2
         */
        public void printValue() {
            System.out.println(RED.name() + "-" + RED.ordinal());
            System.out.println(GREEN.name() + "-" + GREEN.ordinal());
            System.out.println(BLUE.name() + "-" + BLUE.ordinal());
        }
    
        public static void main(String[] args) throws Exception {
    
            serialEnum();
    
    //        deserialEnum();
        }
    
        private static void serialEnum() throws IOException {
            /**
             * 序列化 Color2.GREEN
             */
            Color2.GREEN.printValue();
            ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(PATH));
            oos.writeObject(Color2.GREEN);
            oos.flush();
            oos.close();
        }
    
        private static void deserialEnum() throws Exception {
            //反序列化
            ObjectInputStream ois = new ObjectInputStream(new FileInputStream(PATH));
            Color2 color2 = (Color2) ois.readObject();
            color2.printValue();
    
        }
    }
    

    类结构演化

    例如在序列化后反序列化前将类中增加或者删除某一个属性,对反序列化过程是不会报错的(前提是你将 serialVersionUID 值固定了),如果你是改变的属性的类型,那么肯定是会报错的。

    如果类型发生改变,那么在反序列化时会报如下错误:

    Exception in thread "main" java.io.InvalidClassException: com.example.seriable.Student; incompatible types for field age
        at java.io.ObjectStreamClass.matchFields(ObjectStreamClass.java:2299)
        at java.io.ObjectStreamClass.getReflector(ObjectStreamClass.java:2193)
        at java.io.ObjectStreamClass.initNonProxy(ObjectStreamClass.java:669)
        at java.io.ObjectInputStream.readNonProxyDesc(ObjectInputStream.java:1630)
        at java.io.ObjectInputStream.readClassDesc(ObjectInputStream.java:1521)
        at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:1781)
        at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1353)
        at java.io.ObjectInputStream.readObject(ObjectInputStream.java:373)
        at com.example.seriable.SerializableTest.deserialize(SerializableTest.java:83)
    

    单例的序列化和反射问题

    下面来看一个比较简单的单例实现:

    public class Singleton implements Serializable {
    
        private static final String PATH ="./singleton.bin" ;
        private volatile static  Singleton INSTANCE = null;
    
        private Singleton() {
        }
    
        public static Singleton getInstance() {
    
            if (INSTANCE == null) {
                synchronized (Singleton.class) {
                    if (INSTANCE == null) {
                        INSTANCE = new Singleton();
                    }
                }
            }
            return INSTANCE;
        }
    
    
    
        public static void main(String[] args) throws Exception{
            serialSington();
            deserialSington();
        }
            //序列化
        private static void serialSington() throws IOException {
            ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(PATH));
            oos.writeObject(Singleton.getInstance());
            oos.flush();
            oos.close();
        }
    
        private static void deserialSington() throws Exception {
            //反序列化
            ObjectInputStream ois = new ObjectInputStream(new FileInputStream(PATH));
            Singleton singleton = (Singleton) ois.readObject();
            System.out.println(singleton);
            System.out.println(Singleton.getInstance());
        }
    }
    

    上面的 main 函数的执行结果如下:

    很明显这两个对象是不一样的,违背了单例的原则。

    com.example.singleton.Singleton@58372a00
    com.example.singleton.Singleton@330bedb4
    

    解决方法呢?我们在 Singleton 类中加入一下方法,并返回 INSTANCE 该单例的对象

    private Object readResolve() {
        return INSTANCE;
    }
    

    上面的 main 函数的执行结果如下:

    com.example.singleton.Singleton@330bedb4
    com.example.singleton.Singleton@330bedb4
    

    这样通过 readResolve() 就解决了单列序列化问题了。

    接下来反射问题呢?

    通过一个静态变量 flag 来标识类对象是否已经被创建过,创建过对象之后,如果再次反射去创建那么将会抛出异常。但是呢?我也是可以反射去修改 flag 值呢,所以说单例在反射面前就有点尴尬了。

    public class Singleton2 {
    
        private volatile static Singleton2 INSTANCE = null;
        private static boolean flag = false;
    
        private Singleton2() {
            if (!flag) {
                flag = true;
            } else {
                throw new RuntimeException("单例不允许多次创建");
            }
        }
    
        public static Singleton2 getInstance() {
    
            if (INSTANCE == null) {
                synchronized (Singleton2.class) {
                    if (INSTANCE == null) {
                        INSTANCE = new Singleton2();
                    }
                }
            }
            return INSTANCE;
        }
    
        public static void main(String[] args) throws Exception {
            Singleton2 instance = Singleton2.getInstance();
            Class<Singleton2> singletonClazz = Singleton2.class;
            Constructor<Singleton2> constructor = singletonClazz.getDeclaredConstructor();
            constructor.setAccessible(true);
            Singleton2 singleton2 = constructor.newInstance();
            System.out.println(singleton2);
            System.out.println(instance);
        }
    }
    

    项目地址:https://github.com/liaowjcoder/study4Java/tree/master/10_seriable

    本文是笔者学习之后的总结,方便日后查看学习,有任何不对的地方请指正。

    记录于 2019年5月12号

    相关文章

      网友评论

        本文标题:序列化机制—Serializable需要注意的几点问题

        本文链接:https://www.haomeiwen.com/subject/ljjqaqtx.html