带你了解Java的序列化(Serializable)与反序列化

举报
_陈哈哈 发表于 2022/01/23 01:01:11 2022/01/23
【摘要】 这篇可帮助你大体了解Java中的序列化(Serializable)。包括为什么需要它,如何工作,何时使用它,相关概念(serialVersionUID和transient)以及有关序列化和反序列化的...

这篇可帮助你大体了解Java中的序列化(Serializable)。包括为什么需要它,如何工作,何时使用它,相关概念(serialVersionUID和transient)以及有关序列化和反序列化的其他必要信息。本教程中的序列化示例保持简单,以帮助你理解要点。

1.为什么要进行Java序列化

序列化过程:
是指把一个Java对象变成二进制内容,实质上就是一个byte[]数组。
因为序列化后可以把byte[]保存到文件中,或者把byte[]通过网络传输到远程(IO),这样,就相当于把Java对象存储到文件或者通过网络传输出去了。

反序列化过程:
把一个二进制内容(也就是byte[]数组)变回Java对象。有了反序列化,保存到文件中的byte[]数组又可以“变回”Java对象,或者从网络上读取byte[]并把它“变回”Java对象。

以下是一些使用序列化的示例:

-以面向对象的方式将数据存储到磁盘上的文件,例如,Redis存储Student对象的列表。

-将程序的状态保存在磁盘上,例如,保存游戏状态。

-通过网络以表单对象形式发送数据,例如,在聊天应用程序中以对象形式发送消息。

  
 
  • 1
  • 2
  • 3
  • 4
  • 5

一个Java对象要能序列化,必须实现一个特殊的java.io.Serializable接口,它的定义如下:

public interface Serializable {
}

  
 
  • 1
  • 2

Serializable接口没有定义任何方法,它是一个空接口。我们把这样的空接口称为“标记接口”(Marker Interface),实现了标记接口的类仅仅是给自身贴了个“标记”,并没有增加任何方法。

2.Java中的序列化如何工作

当且仅当对象的类实现java.io.Serializable 接口时,该对象才有资格进行序列化。可序列化 是一个标记接口(不包含任何方法),该接口告诉Java虚拟机(JVM)该类的对象已准备好写入持久性存储或通过网络进行读取。

默认情况下,JVM负责编写和读取可序列化对象的过程。序列化/反序列化功能通过对象流类的以下两种方法公开:

  • ObjectOutputStream。writeObject(Object):将可序列化的对象写入输出流。如果要序列化的某些对象未实现Serializable接口,则此方法将引发NotSerializableException

  • ObjectInputStream。readObject():从输入流读取,构造并返回一个对象。如果找不到序列化对象的类,则此方法将引发ClassNotFoundException
    如果序列化使用的类有问题,则这两种方法都将引发InvalidClassException,如果发生I / O错误,则将引发IOException。无论NotSerializableExceptionInvalidClassException是子类IOException异常。

让我们来看一个简单的例子。以下代码将String对象序列化为名为“ data.ser”的文件。字符串对象是可序列化的,因为String类实现了Serializable 接口:

 
String filePath = "data.ser";
String message = "Java Serialization is Cool";
 
try (
    FileOutputStream fos = new FileOutputStream(filePath);
    ObjectOutputStream outputStream = new ObjectOutputStream(fos);
) {
 
    outputStream.writeObject(message);
 
} catch (IOException ex) {
    System.err.println(ex);
}
 

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

以下代码反序列化文件“ data.ser”中的String对象:

String filePath = "data.ser";
 
try (
    FileInputStream fis = new FileInputStream(filePath);
    ObjectInputStream inputStream = new ObjectInputStream(fis);
) {
 
    String message = (String) inputStream.readObject();
 
    System.out.println("Message: " + message);
 
} catch (ClassNotFoundException ex) {
    System.err.println("Class not found: " + ex);
} catch (IOException ex) {
    System.err.println("IO error: " + ex);
}


  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

请注意,readObject()返回一个Object类型的对象,因此您需要将其强制转换为可序列化的类,在这种情况下为String类。

让我们看一个涉及使用自定义类的更复杂的示例。

给定以下学生班

import java.io.*;
import java.util.*;
 
/**
 * Student.java
 * @author chenhh
 */
public class Student extends Person implements Serializable {
    public static final long serialVersionUID = 1234L;
 
    private long studentId;
    private String name;
    private transient int age;
 
    public Student(long studentId, String name, int age) {
        super();
        this.studentId = studentId;
        this.name = name;
        this.age = age;
 
        System.out.println("Constructor");
    }
 
    public String toString() {
        return String.format("%d - %s - %d", studentId, name, age);
    }
}


  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28

如上面代码,你会发现两点:

-long serialVersionUID类型的常量。

-成员变量age被标记为transient。

  
 
  • 1
  • 2
  • 3

下面让我解释一下它们。

2-1.什么是serialVersionUID常数

serialVersionUID是一个常数,用于唯一标识可序列化类的版本。从输入流构造对象时,JVM在反序列化过程中检查此常数。如果正在读取的对象的serialVersionUID与类中指定的序列号不同,则JVM抛出InvalidClassException。这是为了确保正在构造的对象与具有相同serialVersionUID的类兼容。
请注意,serialVersionUID是可选的。这意味着如果您不显式声明Java编译器,它将生成一个。
那么,为什么要显式声明serialVersionUID呢?
原因是:自动生成的serialVersionUID是基于类的元素(成员变量,方法,构造函数等)计算的。如果这些元素之一发生更改,serialVersionUID也将更改。想象一下这种情况:

-您编写了一个程序,将Student类的某些对象存储到文件中。Student类没有显式声明的serialVersionUID。

-有时,您更新了Student类(例如,添加了一个新的私有方法),现在自动生成的serialVersionUID也被更改了。

-您的程序无法反序列化先前编写的Student对象,因为那里的serialVersionUID不同。JVM抛出InvalidClassException。

  
 
  • 1
  • 2
  • 3
  • 4
  • 5

这就是为什么建议为可序列化类显式添加serialVersionUID的原因。

2-2.什么是瞬时变量?

在上面的Student类中,您看到成员变量age被标记为transient,对吗?JVM 在序列化过程中跳过瞬态变量。这意味着在序列化对象时不会存储age变量的值。
因此,如果成员变量不需要序列化,则可以将其标记为瞬态。
以下代码将Student对象序列化为名为“ students.ser”的文件:

String filePath = "students.ser";
Student student = new Student(123, "John", 22);
 
try (
    FileOutputStream fos = new FileOutputStream(filePath);
    ObjectOutputStream outputStream = new ObjectOutputStream(fos);
) {
 
    outputStream.writeObject(student);
 
} catch (IOException ex) {
    System.err.println(ex);
}

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

请注意,在序列化对象之前,变量age的值为22。
下面的代码从文件中反序列化Student对象:

String filePath = "students.ser";
 
try (
    FileInputStream fis = new FileInputStream(filePath);
    ObjectInputStream inputStream = new ObjectInputStream(fis);
) {
 
    Student student = (Student) inputStream.readObject();
 
    System.out.println(student);
 
} catch (ClassNotFoundException ex) {
    System.err.println("Class not found: " + ex);
} catch (IOException ex) {
    System.err.println("IO error: " + ex);
}

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

此代码将输出以下输出:

1个
123 - John - 0

3.有关Java序列化的更多信息

你应该了解一些有关序列化的重要信息:

  • 序列化一个对象时,它所引用的所有其他对象也会被序列化,依此类推,直到序列化完整的对象树为止。
  • 如果超类实现Serializable,则其子类会自动执行。
  • 反序列化可序列化类的实例时,构造函数将不会运行。
  • 如果超类未实现Serializable,则在反序列化子类对象时,超类构造函数将运行。
  • 静态变量未序列化,因为它们不是对象本身的一部分。
  • 如果序列化集合或数组,则每个元素都必须可序列化。单个不可序列化的元素将导致序列化失败(NotSerializableException)。
  • JDK中的可序列化类包括原始包装器(Integer,Long,Double等),String,Date,collection类…对于其他类,请查阅相关的Javadoc来了解它们是否可序列化。

文章来源: chensj.blog.csdn.net,作者:_陈哈哈,版权归原作者所有,如需转载,请联系作者。

原文链接:chensj.blog.csdn.net/article/details/105104824

【版权声明】本文为华为云社区用户转载文章,如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。