Java 序列化和反序列化为什么要实现 Serializable 接口

简介: 我是小假 期待与你的下一次相遇 ~

序列化和反序列化

  • 序列化:把对象转换为字节序列的过程称为对象的序列化。
  • 反序列化:把字节序列恢复为对象的过程称为对象的反序列化。
    什么时候需要用到序列化和反序列化呢?
    只在本地JVM里运行下Java实例,这个时候是不需要什么序列化和反序列化的,但当内存中的对象持久化到磁盘,数据库中时,需要与浏览器进行交互时,当需要实现RPC时,这个时候就需要序列化和反序列化了。
    前两个需要用到序列化和反序列化的场景,是不是有一个很大的疑问? 在平常与浏览器交互时,还有将内存中的对象持久化到数据库中时,好像都没有进行序列化和反序列化,都没有实现Serializable接口,但一直正常运行。
    原因是:
    只要对内存中的对象进行持久化或网络传输,这个时候都需要序列化和反序列化。
    理由:
    服务器与浏览器交互时有用到Serializable接口,JSON格式实际上就是将一个对象转化为字符串,所以服务器与浏览器交互时的数据格式其实是字符串,来看String类型的源码:
  1. public final class String
  2.  implements java.io.Serializable, Comparable<String>, CharSequence {
  3.  /** The value is used for character storage. */
  4.  private final char value[];
  5.  /** Cache the hash code for the string */
  6.  private int hash; // Default to 0
  7.  /** use serialVersionUID from JDK 1.0.2 for interoperability */
  8.  private static final long serialVersionUID = -6849794470754667710L;
  9.  ......
  10. }
  • String类型实现了Serializable接口,并显示指定serialVersionUID的值。
    然后再来看对象持久化到数据库中时的情况,Mybatis数据库映射文件里的insert代码:
  1. <insert id="insertUser" parameterType="org.fcant.bean.User">
  2.  INSERT INTO t_user(name, age) VALUES (#{name}, #{age})
  3. </insert>
  • 实际上并不是将整个对象持久化到数据库中,而是将对象中的属性持久化到数据库中,而这些属性都是实现了Serializable接口的基本属性。
    实现序列化和反序列化实现Serializable接口的作用
    在Java中实现了Serializable接口后,JVM会在底层实现序列化和反序列化,如果不实现Serializable接口,也可以自己写一套序列化和反序列化代码。
    实现Serializable接口时为什么还要显示指定serialVersionUID的值?
    如果不显示指定serialVersionUID,JVM在序列化时会根据属性自动生成一个serialVersionUID,然后与属性一起序列化,再进行持久化或网络传输。在反序列化时,JVM会再根据属性自动生成一个新版serialVersionUID,然后将这个新版serialVersionUID与序列化时生成的旧版serialVersionUID进行比较,如果相同则反序列化成功,否则报错。
    如果显示指定了serialVersionUID,JVM在序列化和反序列化时仍然都会生成一个serialVersionUID,但值为指定的值,这样在反序列化时新旧版本的serialVersionUID就一致了。
    在实际开发中,不显示指定serialVersionUID的情况会导致什么问题? 如果类写完后不再修改,不会有问题,但这在实际开发中是不可能的,项目中的类会不断迭代,一旦类被修改了,那么旧对象反序列化就会报错。所以在实际开发中,都会显示指定一个serialVersionUID,值是多少无所谓,只要不变就行。
    通过实例测试下:
    (1) User类
    不显示指定serialVersionUID
  1. public class User implements Serializable {
  2.  private String name;
  3.  private Integer age;
  4.  public String getName() {
  5.      return name;
  6.  }
  7.  public void setName(String name) {
  8.      this.name = name;
  9.  }
  10.  public Integer getAge() {
  11.      return age;
  12.  }
  13.  public void setAge(Integer age) {
  14.      this.age = age;
  15.  }
  16.  @Override
  17.  public String toString() {
  18.      return "User{" +
  19.              "name='" + name + '\'' +
  20.              ", age=" + age +
  21.              '}';
  22.  }
  23. }

  • (2) 测试类
    先进行序列化,再进行反序列化。 ```java public class SerializableTest {
    private static void serialize(User user) throws Exception {
  1.  ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(new File("D:\\111.txt")));
  2.  oos.writeObject(user);
  3.  oos.close();
  • }
    private static User deserialize() throws Exception{
  1.  ObjectInputStream ois = new ObjectInputStream(new FileInputStream(new File("D:\\111.txt")));
  2.  return (User) ois.readObject();
  • }
  1. public static void main(String[] args) throws Exception {
  2.    User user = new User();
  3.    user.setName("tyshawn");
  4.    user.setAge(18);
  5.    System.out.println("序列化前的结果: " + user);
  6.    serialize(user);
  7.    User dUser = deserialize();
  8.    System.out.println("反序列化后的结果: "+ dUser);
  9. }

}

  1. <a name="TRkNB"></a>
  2. #### (3) 结果
  3. 先注释掉反序列化代码,执行序列化代码,然后User类新增一个属性sex
  4. ```java
  5. public class User implements Serializable {
  6.    private String name;
  7.    private Integer age;
  8.    private String sex;
  9.    public String getName() {
  10.        return name;
  11.    }
  12.    public void setName(String name) {
  13.        this.name = name;
  14.    }
  15.    public Integer getAge() {
  16.        return age;
  17.    }
  18.    public void setAge(Integer age) {
  19.        this.age = age;
  20.    }
  21.    public String getSex() {
  22.        return sex;
  23.    }
  24.    public void setSex(String sex) {
  25.        this.sex = sex;
  26.    }
  27.    @Override
  28.    public String toString() {
  29.        return "User{" +
  30.                "name='" + name + '\'' +
  31.                ", age=" + age +
  32.                ", sex='" + sex + '\'' +
  33.                '}';
  34.    }
  35. }

再注释掉序列化代码执行反序列化代码,最后结果如下:

  1. 序列化前的结果: User{name='tyshawn', age=18}
  2. Exception in thread "main" java.io.InvalidClassException: org.tyshawn.SerializeAndDeserialize.User; local class incompatible: stream classdesc serialVersionUID = 1035612825366363028, local class serialVersionUID = -1830850955895931978

报错结果为序列化与反序列化产生的serialVersionUID不一致。

接下来在上面User类的基础上显示指定一个serialVersionUID

  1. private static final long serialVersionUID = 1L;

再执行上述步骤,测试结果如下:

  1. 序列化前的结果: User{name='tyshawn', age=18}
  2. 反序列化后的结果: User{name='tyshawn', age=18, sex='null'}

显示指定serialVersionUID后就解决了序列化与反序列化产生的serialVersionUID不一致的问题。

Java序列化的其他特性

transient关键字修饰的属性不会被序列化,static属性也不会被序列化。

测试下这个结论:

(1) User类

  1. public class User implements Serializable {
  2.    private static final long serialVersionUID = 1L;
  3.    private String name;
  4.    private Integer age;
  5.    private transient String sex;
  6.    private static String signature = "你眼中的世界就是你自己的样子";
  7.    public String getName() {
  8.        return name;
  9.    }
  10.    public void setName(String name) {
  11.        this.name = name;
  12.    }
  13.    public Integer getAge() {
  14.        return age;
  15.    }
  16.    public void setAge(Integer age) {
  17.        this.age = age;
  18.    }
  19.    public String getSex() {
  20.        return sex;
  21.    }
  22.    public void setSex(String sex) {
  23.        this.sex = sex;
  24.    }
  25.    public static String getSignature() {
  26.        return signature;
  27.    }
  28.    public static void setSignature(String signature) {
  29.        User.signature = signature;
  30.    }
  31.    @Override
  32.    public String toString() {
  33.        return "User{" +
  34.                "name='" + name + '\'' +
  35.                ", age=" + age +
  36.                ", sex='" + sex +'\'' +
  37.                ", signature='" + signature + '\'' +
  38.                '}';
  39.    }
  40. }

(2) 测试类

  1. public class SerializableTest {
  2.    private static void serialize(User user) throws Exception {
  3.        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(new File("D:\\111.txt")));
  4.        oos.writeObject(user);
  5.        oos.close();
  6.    }
  7.    private static User deserialize() throws Exception{
  8.        ObjectInputStream ois = new ObjectInputStream(new FileInputStream(new File("D:\\111.txt")));
  9.        return (User) ois.readObject();
  10.    }
  11.    public static void main(String[] args) throws Exception {
  12.        User user = new User();
  13.        user.setName("tyshawn");
  14.        user.setAge(18);
  15.        user.setSex("man");
  16.        System.out.println("序列化前的结果: " + user);
  17.        serialize(user);
  18.        User dUser = deserialize();
  19.        System.out.println("反序列化后的结果: "+ dUser);
  20.    }
  21. }

(3) 结果

先注释掉反序列化代码,执行序列化代码,然后修改User类signature = "我的眼里只有你",再注释掉序列化代码执行反序列化代码,最后结果如下:

  1. 序列化前的结果: User{name='tyshawn', age=18, sex='man', signature='你眼中的世界就是你自己的样子'}
  2. 反序列化后的结果: User{name='tyshawn', age=18, sex='null', signature='我的眼里只有你'}

static属性为什么不会被序列化

因为序列化是针对对象而言的,而static属性优先于对象存在,随着类的加载而加载,所以不会被序列化。

看到这个结论,是不是有人会问,serialVersionUID也被static修饰,为什么serialVersionUID会被序列化? 其实serialVersionUID属性并没有被序列化,JVM在序列化对象时会自动生成一个serialVersionUID,然后将显示指定的serialVersionUID属性值赋给自动生成的serialVersionUID


相关文章
|
3月前
|
数据采集 JSON Java
Java爬虫获取1688店铺所有商品接口数据实战指南
本文介绍如何使用Java爬虫技术高效获取1688店铺商品信息,涵盖环境搭建、API调用、签名生成及数据抓取全流程,并附完整代码示例,助力市场分析与选品决策。
|
3月前
|
消息中间件 缓存 前端开发
从资损百万到零事故:Java 接口幂等设计的艺术与实践
在分布式系统中,重复请求常引发严重资损,如支付双扣、库存超卖等问题,其根源在于接口缺乏幂等性设计。本文通过真实案例揭示幂等性的重要性,并详解8种主流解决方案,涵盖唯一请求ID、乐观锁、悲观锁、状态机等,帮助开发者构建稳定系统,保障业务一致性。无论你是架构师还是开发工程师,都能从中获得实战指导,有效规避重复调用带来的风险。
196 0
|
28天前
|
JSON 网络协议 安全
【Java】(10)进程与线程的关系、Tread类;讲解基本线程安全、网络编程内容;JSON序列化与反序列化
几乎所有的操作系统都支持进程的概念,进程是处于运行过程中的程序,并且具有一定的独立功能,进程是系统进行资源分配和调度的一个独立单位一般而言,进程包含如下三个特征。独立性动态性并发性。
95 1
|
28天前
|
Java Go 开发工具
【Java】(9)抽象类、接口、内部的运用与作用分析,枚举类型的使用
抽象类必须使用abstract修饰符来修饰,抽象方法也必须使用abstract修饰符来修饰,抽象方法不能有方法体。抽象类不能被实例化,无法使用new关键字来调用抽象类的构造器创建抽象类的实例。抽象类可以包含成员变量、方法(普通方法和抽象方法都可以)、构造器、初始化块、内部类(接 口、枚举)5种成分。抽象类的构造器不能用于创建实例,主要是用于被其子类调用。抽象类中不一定包含抽象方法,但是有抽象方法的类必定是抽象类abstract static不能同时修饰一个方法。
169 1
|
28天前
|
JSON 网络协议 安全
【Java基础】(1)进程与线程的关系、Tread类;讲解基本线程安全、网络编程内容;JSON序列化与反序列化
几乎所有的操作系统都支持进程的概念,进程是处于运行过程中的程序,并且具有一定的独立功能,进程是系统进行资源分配和调度的一个独立单位一般而言,进程包含如下三个特征。独立性动态性并发性。
97 2
|
2月前
|
算法 安全 Java
除了类,Java中的接口和方法也可以使用泛型吗?
除了类,Java中的接口和方法也可以使用泛型吗?
104 11
|
3月前
|
存储 缓存 安全
Java集合框架(二):Set接口与哈希表原理
本文深入解析Java中Set集合的工作原理及其实现机制,涵盖HashSet、LinkedHashSet和TreeSet三大实现类。从Set接口的特性出发,对比List理解去重机制,并详解哈希表原理、hashCode与equals方法的作用。进一步剖析HashSet的底层HashMap实现、LinkedHashSet的双向链表维护顺序特性,以及TreeSet基于红黑树的排序功能。文章还包含性能对比、自定义对象去重、集合运算实战和线程安全方案,帮助读者全面掌握Set的应用与选择策略。
201 23
|
3月前
|
安全 Java 开发者
Java集合框架:详解Deque接口的栈操作方法全集
理解和掌握这些方法对于实现像浏览器后退功能这样的栈操作来说至关重要,它们能够帮助开发者编写既高效又稳定的应用程序。此外,在多线程环境中想保证线程安全,可以考虑使用ConcurrentLinkedDeque,它是Deque的线程安全版本,尽管它并未直接实现栈操作的方法,但是Deque的接口方法可以相对应地使用。
187 12
|
3月前
|
存储 安全 Java
Java集合框架(一):List接口及其实现类剖析
本文深入解析Java中List集合的实现原理,涵盖ArrayList的动态数组机制、LinkedList的链表结构、Vector与Stack的线程安全性及其不推荐使用的原因,对比了不同实现的性能与适用场景,帮助开发者根据实际需求选择合适的List实现。