• 110.Java对象的序列化


    对象的序列化

    当创建对象时,程序运行时它就会存在,但是程序停止时,对象也就消失了.但是如果希望对象在程序不运行的情况下仍能存在并保存其信息,将会非常有用,对象将被重建并且拥有与程序上次运行时拥有的信息相同。可以使用对象的序列化。

     对象的序列化:   将内存中的对象直接写入到文件设备中

     对象的反序列化: 将文件设备中持久化的数据转换为内存对象

    基本的序列化由两个方法产生:一个方法用于序列化对象并将它们写入一个流,另一个方法用于读取流并反序列化对象。

    ObjectOutput
    writeObject(Object obj) 
              将对象写入底层存储或流。
    ObjectInput
    readObject() 
              读取并返回对象。

     ObjectOutputStream

     ObjectInputStream

    由于上述ObjectOutput和ObjectInput是接口,所以需要使用具体实现类。

    ObjectOutput
     ObjectOutputStream被写入的对象必须实现一个接口:Serializable
    否则会抛出:NotSerializableException 
    ObjectInput
         ObjectInputStream    该方法抛出异常:ClassNotFountException     

    ObjectOutputStream和ObjectInputStream 对象分别需要字节输出流和字节输入流对象来构建对象。也就是这两个流对象需要操作已有对象将对象进行本地持久化存储。

    案例:

    序列化和反序列化Cat对象。

    public class Demo3 {
        public static void main(String[] args) throws IOException,
                ClassNotFoundException {
            Cat cat = new Cat("tom", 3);
            FileOutputStream fos = new FileOutputStream(new File("c:\Cat.txt"));
            ObjectOutputStream oos = new ObjectOutputStream(fos);
            oos.writeObject(cat);
            System.out.println(cat);
            oos.close();
            // 反序列化
            FileInputStream fis = new FileInputStream(new File("c:\Cat.txt"));
            ObjectInputStream ois = new ObjectInputStream(fis);
            Object readObject = ois.readObject();
            Cat cat2 = (Cat) readObject;
            System.out.println(cat2);
            fis.close();
    }
    
    class Cat implements Serializable {
        public String name;
        public int age;
    
        public Cat() {
    
        }
    
        public Cat(String name, int age) {
    
            this.name = name;
            this.age = age;
        }
    
        @Override
        public String toString() {
            return "Cat [name=" + name + ", age=" + age + "]";
        }
    
    }

    例子关键点:

    1. 声明Cat类实现了Serializable接口。是一个标示器,没有要实现的方法。
    2. 新建Cat对象。
    3. 新建字节流对象(FileOutputStream)进序列化对象保存在本地文件中。
    4. 新建ObjectOutputStream对象,调用writeObject方法序列化Cat对象。
    5. writeObject方法会执行两个工作:序列化对象,然后将序列化的对象写入文件中。
    6. 反序列化就是调用ObjectInputStream的readObject()方法。
    7. 异常处理和流的关闭动作要执行。

    Serializable:

    类通过实现 java.io.Serializable 接口以启用其序列化功能。未实现此接口的类将无法使其任何状态序列化或反序列化。可序列化类的所有子类型本身都是可序列化的。序列化接口没有方法或字段,仅用于标识可序列化的语义。

    所以需要被序列化的类必须是实现Serializable接口,该接口中没有描述任何的属性和方法,称之为标记接口。

    如果对象没有实现接口Serializable,在进行序列化时会抛出:NotSerializableException 异常。

    注意:

    保存一个对象的真正含义是什么?如果对象的实例变量都是基本数据类型,那么就非常简单。但是如果实例变量是包含对象的引用,会怎么样?保存的会是什么?很显然在Java中保存引用变量的实际值没有任何意义,因为Java引用的值是通过JVM的单一实例的上下文中才有意义。通过序列化后,尝试在JVM的另一个实例中恢复对象,是没有用处的。

    如下:

    首先建立一个Dog对象,也建立了一个Collar对象。Dog中包含了一个Collar(项圈)

    现在想要保存Dog对象,但是Dog中有一个Collar,意味着保存Dog时也应该保存Collar。假如Collar也包含了其他对象的引用,那么会发生什么?意味着保存一个Dog对象需要清楚的知道Dog对象的内部结构。会是一件很麻烦的事情。

       Java的序列化机制可以解决该类问题,当序列化一个对象时,Java的序列化机制会负责保存对象的所有关联的对象(就是对象图),反序列化时,也会恢复所有的相关内容。本例中:如果序列化Dog会自动序列化Collar。但是,只有实现了Serializable接口的类才可以序列化。如果只是Dog实现了该接口,而Collar没有实现该接口。会发生什么?

    Dog类和Collar类

    import java.io.Serializable;
    
    public class Dog implements Serializable {
        private Collar collar;
        private String name;
    
        public Dog(Collar collar, String name) {
    
            this.collar = collar;
            this.name = name;
        }
    
        public Collar getCollar() {
            return collar;
        }
    
    }
    
    class Collar {
        private int size;
    
        public int getSize() {
            return size;
        }
    
        public Collar(int size) {
            this.size = size;
        }
    
    }

    序列化

    import java.io.File;
    import java.io.FileOutputStream;
    import java.io.IOException;
    import java.io.ObjectOutputStream;
    
    public class Demo4 {
        public static void main(String[] args) throws IOException {
            Collar coll = new Collar(10);
            Dog dog = new Dog(coll, "旺财");
    
            FileOutputStream fis = new FileOutputStream(new File("c:\dog.txt"));
            ObjectOutputStream os = new ObjectOutputStream(fis);
            os.writeObject(dog);
        }
    }

    执行程序,出现了运行时异常。

    Exception in thread "main" java.io.NotSerializableException: Collar

    所以我们也必须将Dog中使用的Collar序列化。但是如果我们无法访问Collar的源代码,或者无法使Collar可序列化,如何处理?

    两种解决方法:

    一:继承Collar类,使子类可序列化

    但是:如果Collar是final类,就无法继承了。并且,如果Collar引用了其他非序列化对象,也无法解决该问题。

    transient

    此时就可以使用transient修饰符,可以将Dog类中的成员变量标识为transient

    那么在序列化Dog对象时,序列化就会跳过Collar。

    public class Demo4 {
        public static void main(String[] args) throws IOException,
                ClassNotFoundException {
            Collar coll = new Collar(10);
            Dog dog = new Dog(coll, "旺财");
            System.out.println(dog.getCollar().getSize());
    
            FileOutputStream fis = new FileOutputStream(new File("c:\dog.txt"));
            ObjectOutputStream os = new ObjectOutputStream(fis);
            os.writeObject(dog);
    
            // 反序列化
            FileInputStream fos = new FileInputStream(new File("c:\dog.txt"));
            ObjectInputStream ois = new ObjectInputStream(fos);
            Object readObject = ois.readObject();
            Dog dog2 = (Dog) readObject;
            // Collar未序列化。
            dog2.getCollar().getSize();
        }
    }

    这样我们具有一个序列化的Dog和非序列化的Collar。

    此时反序列化Dog后,访问Collar,就会出现运行时异常

    10
    Exception in thread "main" java.lang.NullPointerException

    注意:序列化不适用于静态变量,因为静态变量并不属于对象的实例变量的一部分。静态变量随着类的加载而加载,是类变量。由于序列化只适用于对象。

    基本数据类型可以被序列化

    public class Demo5 {
        public static void main(String[] args) throws IOException {
            // 创建序列化流对象
            FileOutputStream fis = new FileOutputStream(new File("c:\basic.txt"));
            ObjectOutputStream os = new ObjectOutputStream(fis);
            // 序列化基本数据类型
            os.writeDouble(3.14);
            os.writeBoolean(true);
            os.writeInt(100);
            os.writeInt(200);
    
            // 关闭流
            os.close();
    
            // 反序列化
            FileInputStream fos = new FileInputStream(new File("c:\basic.txt"));
            ObjectInputStream ois = new ObjectInputStream(fos);
    
            System.out.println(ois.readDouble());
            System.out.println(ois.readBoolean());
            System.out.println(ois.readInt());
            System.out.println(ois.readInt());
    
            fos.close();
        }
    }

    serialVersionUID

    用于给类指定一个UID。该UID是通过类中的可序列化成员的数字签名运算出来的一个long型的值。

    只要是这些成员没有变化,那么该值每次运算都一样。

    该值用于判断被序列化的对象和类文件是否兼容。

    如果被序列化的对象需要被不同的类版本所兼容。可以在类中自定义UID。

    定义方式:static final long serialVersionUID = 42L;

    author@nohert
  • 相关阅读:
    Python 学习笔记(七)Python字符串(三)
    Python 学习笔记(七)Python字符串(二)
    Python 学习笔记(六)Python第一个程序
    Python 学习笔记(五)常用函数
    Python 学习笔记(四)数字(二)
    行为型模式之责任链模式
    python_frm组件
    django之models学习总结
    HTTP协议
    事件委托
  • 原文地址:https://www.cnblogs.com/gzgBlog/p/13665147.html
Copyright © 2020-2023  润新知