对于Serializable的理解

对于Serializable的理解

Last Edited: Apr 04, 2019 2:53 PM
Tags: java

开始

序列化:把Java对象转换为字节序列的过程。

反序列化:把字节序列恢复为Java对象的过程。

序列化的解释

在Java中,我们可以通过多种方式来创建对象,并且只要对象没有被回收我们都可以复用该对象,但是,我们创建出来的这些**Java对象都是存在于JVM的堆内存中的。**只有JVM处于运行状态的时候,这些对象才可能存在,一单JVM停止运行,这些对象的状态也就随之丢失了。

但是在真是的应用场景中,我们需要将这些对象持久化下来,并且能够在需要的时候把对象重新读取出来。Java对象序列化就可以帮助我们实现该功能。

测试

这样说的话我们可能不怎么会理解,我们直接上手代码。现在我们有一个需求就是把一个对象保存到文件中,我们现在简单的测试一下。

创建一个最简单的Student类


    public class Student {
        private  int id;
        public Student(int id) {
            this.id = id;
        }
        public int getId() {
            return id;
        }
        public void setId(int id) {
            this.id = id;
        }
        @Override
        public String toString() {
            return "Student{" +
                    "id=" + id +
                    '}';
        }
    }

接下来创建一个类用于输出对象到文件中。

    @Test
        public void main() throws Exception{
            Student student = new Student(1);
            FileOutputStream fos = new FileOutputStream("out.txt");
            ObjectOutputStream oos = new ObjectOutputStream(fos);
            oos.writeObject(student);
            oos.close();
        }

测试结果是什么呢,直接运行的话系统会直接报 java.io.NotSerializableException

哪我们现在实现 serializable接口重新试试看,将Student类修改为

    public class Student implements Serializable {
        private  int id;
        public Student(int id) {
            this.id = id;
        }
        public int getId() {
            return id;
        }
        public void setId(int id) {
            this.id = id;
        }
        @Override
        public String toString() {
            return "Student{" +
                    "id=" + id +
                    '}';
        }
    }

结果当然没有问题啦,成功的执行了,那么下面我们从文件中获取对象试试看。

    @Test
        public void main1() throws Exception{
            ObjectInputStream ois =
                    new ObjectInputStream(
                            new FileInputStream(new File("out.txt")));
            Student student = (Student)ois.readObject();
            ois.close();
            System.out.println(student);
        }

程序并不会报错,完整的输出了 Student{id=1}

在这之前,我们一直没有把 serialVersionUID 这个参数加入在程序中,现在我们在Student类中加入

    private static final long serialVersionUID = 1L;

重新运行main1 主函数试试看(这里其实用到的还是没有吧serialVersionUID参数加入Student类之前,用main函数生成的out.txt文件)

程序会报

    java.io.InvalidClassException: com.liuyanzhao.blog.test.SerializableTest.Student; local class incompatible: stream classdesc serialVersionUID = -1350335207596260519, local class serialVersionUID = 1

从上面可以看出,当我们没有定义serialVersionUID参数的时候,系统自动帮我们生成serialVersionUID 。当我们手动给serialVersionUID赋值1L之后,再重新去读取之前那个对象,serialVersionUID不同,程序报错。

我们仔细想一想,在报错信息中,已经把系统给我们自动定义的serialVersionUID给出来了,我们不就直接可以把 报错信息中的stream classdesc serialVersionUID = -1350335207596260519 弄到我们的Student类里面去。

话不多说,我们直接测试,我们把Student中的 private static final long serialVersionUID = 1L;改为

    private static final long serialVersionUID = -1350335207596260519L;

执行,main1主函数,果然,跟我们想的一样,程序成功的执行了。

根据上面的实验结果我们可以总结出来三个特点

  • 要想把一个对象写入文件,我们需要实现Serializable接口
  • 当没有定义serialVersionUID的时候,系统会自动给我们创建
  • 只有当serialVersionUID一致的时候,才能成功的从文件中读取之前保存到对象

静态变量序列化

在理解上面的测试之后,我们开始一些进阶的测试,静态变量的序列化,我们修改Student中的代码如下

    public class Student implements Serializable {
        private static final long serialVersionUID = -1350335207596260519L;
        private int id;
        private static  String name;
        public Student(int id) {
            this.id = id;
        }
        public int getId() {
            return id;
        }
        public void setId(int id) {
            this.id = id;
        }
        public static String getName() {
            return name;
        }
        public static void setName(String name) {
            Student.name = name;
        }
        @Override
        public String toString() {
            return "Student{" +
                    "id=" + id +
                    " name = " + getName() + '}';
        }
    }

在上面的代码中,我们添加一个静态的name变量,并重构了toString方法,接下来就用main2主函数测试

    @Test
        public void main2() throws Exception{
            Student student = new Student(1);
            Student.setName("XXX");
            System.out.println(student);
            FileOutputStream fos = new FileOutputStream("out.txt");
            ObjectOutputStream oos = new ObjectOutputStream(fos);
            oos.writeObject(student);
            oos.close();
        }

运行上面的代码将对象写入到文件中之后,在次运行main1主函数读取在文件中的对象,结果如下

Student{id=1 name = null}

可以看出,静态变量并未被写入文件。贴上一段复制过来的文本

串行化只能保存对象的非静态成员交量,不能保存任何的成员方法和静态的成员变量,而且串行化保存的只是变量的值,对于变量的任何修饰符都不能保存。
如果把Student类中的name定义为static类型的话,试图重构,就不能得到原来的值,只能得到null。说明对静态成员变量值是不保存的。这其实比较容易理解,序列化保存的是对象的状态,静态变量属于类的状态,因此 序列化并不保存静态变量。

transient关键字

简单来说,当某些变量不想被写入文件,同是又不适合使用static关键字声明,那么此时就需要用transient关键字来声明该变量。例如,把Student类中的静态变量name改为

    private transient String name;

接下来重复之前的测试方法给对象赋值,把对象写入文件,从文件中读取对象,可以得到跟static关键字定义的name一样的结果。

在被反序列化后,transient 变量的值被设为初始值,如 int 型的是 0,对象型的是 null。

注:对于某些类型的属性,其状态是瞬时的,这样的属性是无法保存其状态的。例如一个线程属性或需要访问IO、本地资源、网络资源等的属性,对于这些字段,我们必须用transient关键字标明,否则编译器将报措。

序列化中的继承问题

我觉得就只有一个重点,

  • 当一个父类实现序列化,子类自动实现序列化,不需要显式实现Serializable接口。

通过上面的一些测试,我们大概知道了Serializable接口的作用,接下来就是序列化的用途

序列化的用途

  • 想把的内存中的对象状态保存到一个文件中或者数据库中时候
  • 想把对象通过网络进行传播的时候

结语

上面的仅仅是我自己的理解,里面并没用用太多的专业词汇,因为我也不懂,嘿嘿,

参考文章

https://blog.csdn.net/u011568312/article/details/57611440

一些更好的理解

一个对象序列化的接口,一个类只有实现了Serializable接口,它的对象才是可序列化的。因此如果要序列化某些类的对象,这些类就必须实现Serializable接口。而实际上,Serializable是一个空接口,没有什么具体内容,它的目的只是简单的标识一个类的对象可以被序列化。
什么情况下需要序列化
a)当你想把的内存中的对象写入到硬盘的时候;
b)当你想用套接字在网络上传送对象的时候;
c)当你想通过RMI传输对象的时候;
再稍微解释一下:a)比如说你的内存不够用了,那计算机就要将内存里面的一部分对象暂时的保存到硬盘中,等到要用的时候再读入到内存中,硬盘的那部分存储空间就是所谓的虚拟内存。在比如过你要将某个特定的对象保存到文件中,我隔几天在把它拿出来用,那么这时候就要实现Serializable接口;
b)在进行java的Socket编程的时候,你有时候可能要传输某一类的对象,那么也就要实现Serializable接口;最常见的你传输一个字符串,它是JDK里面的类,也实现了Serializable接口,所以可以在网络上传输。
c)如果要通过远程的方法调用(RMI)去调用一个远程对象的方法,如在计算机A中调用另一台计算机B的对象的方法,那么你需要通过JNDI服务获取计算机B目标对象的引用,将对象从B传送到A,就需要实现序列化接口。

原文地址:https://www.cnblogs.com/neverth/p/11760940.html