序列化与反序列化

x33g5p2x  于2022-03-11 转载在 其他  
字(3.3k)|赞(0)|评价(0)|浏览(473)

序列化与反序列化

一、基本概念

序列化:将java对象转换为字节序列

反序列化:把字节序列恢复成原本的java对象
序列化的作用:在传递和保存对象时,保证对象的完整性和可传递性。对象转换为有序字节流,也方便在网络上传输或者保存到本地文件

反序列化的作用:根据字节流中保存的对象状态集描述信息,通过反序列化重建对象

二、java对象如何序列化

Student类定义

@Data
@AllArgsConstructor
@NoArgsConstructor
@ToString
public class Student implements Serializable {
 
    private String name;
    private Integer age;
    private Integer score;

}

序列化

public class DemoSerializable {
    public static void main(String[] args) throws IOException {
        Student student = new Student();
        student.setName("CodeSheep");
        student.setAge(18);
        student.setScore(1000);

        ObjectOutputStream objectOutputStream =
                new ObjectOutputStream(new FileOutputStream("student.txt"));
        objectOutputStream.writeObject(student);
        objectOutputStream.close();

        System.out.println("序列化成功!已经生成student.txt文件");
        System.out.println("==============================================");

    }
}

反序列化

public class DemoDeserialize {
    public static void main(String[] args) throws IOException, ClassNotFoundException {
        ObjectInputStream objectInputStream =
                new ObjectInputStream( new FileInputStream("student.txt") );
        Student student = (Student) objectInputStream.readObject();
        objectInputStream.close();

        System.out.println("反序列化结果为:");
        System.out.println( student );
    }
}

三、Serializable接口的作用

这里我们可以点进去Serializable接口,查看源码,发现这是一个空接口

这里我们试想一下,加入Student类不实现Serializable接口,会发送什么情况呢?

此时的程序运行会报错,并抛出NotSerializableException异常:

我们按照错误提示,由源码一直跟到ObjectOutputStreamwriteObject0()方法底层看一看:

查看源码可知:如果一个对象既不是字符串、数组、枚举,而且也没有实现Serializable接口的话,在序列化时就会抛出NotSerializableException异常!
Serializable接口也仅仅只是做一个标记用,它告诉代码只要是实现了Serializable接口的类都是可以被序列化的!然而真正的序列化动作不需要靠它完成

四、serialVersionUID的作用

我们有时候可以在一些类中看到如下的代码,那他到底有什么作用呢?

private static final long serialVersionUID = -4392658638228508589L;

下面我们来做一个小实验,先让Student类实现Serializable接口,然后执行序列化的方法

public class DemoSerializable {
    public static void main(String[] args) throws IOException {
        Student student = new Student();
        student.setName("CodeSheep");
        student.setAge(18);
        student.setScore(1000);

        ObjectOutputStream objectOutputStream =
                new ObjectOutputStream(new FileOutputStream("student.txt"));
        objectOutputStream.writeObject(student);
        objectOutputStream.close();

        System.out.println("序列化成功!已经生成student.txt文件");
        System.out.println("==============================================");

    }
}

然后在student类添加一个字段

这时候,我们拿刚才已经序列化到本地的student.txt文件,还用如下代码进行反序列化,试图还原出刚才那个Student对象:

public class DemoDeserialize {
    public static void main(String[] args) throws IOException, ClassNotFoundException {
        ObjectInputStream objectInputStream =
                new ObjectInputStream( new FileInputStream("student.txt") );
        Student student = (Student) objectInputStream.readObject();
        objectInputStream.close();

        System.out.println("反序列化结果为:");
        System.out.println( student );
    }
}

这地方提示的信息非常明确了:序列化前后的serialVersionUID号码不兼容!
结论:

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

serialVersionUID序列化ID,可以看成是序列化和反序列化过程中的“暗号”,在反序列化时,JVM会把字节流中的序列号ID和被序列化类中的序列号ID做比对,只有两者一致,才能重新反序列化,否则就会报异常来终止反序列化的过程

如果在定义一个可序列化的类时,没有人为显式地给它定义一个serialVersionUID的话,则Java运行时环境会根据该类的各方面信息自动地为它生成一个默认的serialVersionUID,一旦像上面一样更改了类的结构或者信息,则类的serialVersionUID也会跟着变化!

所以,为了serialVersionUID的确定性,写代码时还是建议,凡是implements Serializable的类,都最好人为显式地为它声明一个serialVersionUID明确值

五、俩种特殊情况

  • 凡是被static修饰的字段是不会被序列化的
  • 凡是被transient修饰符修饰的字段也是不会被序列化的

对于第一点,因为序列化保存的是对象的状态而非类的状态,所以会忽略static静态域也是理所应当的

对于第二点,就需要了解一下transient修饰符的作用了

如果在序列化某个类的对象时,就是不希望某个字段被序列化(比如这个字段存放的是隐私值,如:密码等),那这时就可以用transient修饰符来修饰该字段

相关文章