关于后端:Java-序列化和反序列化为什么要实现-Serializable-接口

1次阅读

共计 5827 个字符,预计需要花费 15 分钟才能阅读完成。

当你无奈从一楼蹦到三楼时,不要遗记走楼梯。要记住平凡的胜利往往不是欲速不达的,必须学会合成你的指标,逐渐施行。

公司的零碎在做服务化, 须要把所有 model 包里的类都实现 Serializable 接口, 同时还要显示指定 serialVersionUID 的值。听到这个需要, 我脑海里忽然呈现了好几个问题, 比如说:

  • 序列化和反序列化是什么?
  • 实现序列化和反序列化为什么要实现 Serializable 接口?
  • 实现 Serializable 接口就算了, 为什么还要显示指定 serialVersionUID 的值?
  • 我要为 serialVersionUID 指定个什么值?

上面咱们来一一解答这几个问题.

序列化和反序列化

  • 序列化:把对象转换为字节序列的过程称为对象的序列化.
  • 反序列化:把字节序列复原为对象的过程称为对象的反序列化.

什么时候须要用到序列化和反序列化呢?

当咱们只在本地 JVM 里运行下 Java 实例, 这个时候是不须要什么序列化和反序列化的, 但当咱们须要将内存中的对象长久化到磁盘, 数据库中时, 当咱们须要与浏览器进行交互时, 当咱们须要实现 RPC 时, 这个时候就须要序列化和反序列化了.

前两个须要用到序列化和反序列化的场景, 是不是让咱们有一个很大的疑难? 咱们在与浏览器交互时, 还有将内存中的对象长久化到数据库中时, 如同都没有去进行序列化和反序列化, 因为咱们都没有实现 Serializable 接口, 但始终失常运行.

上面先给出论断:

只有咱们对内存中的对象进行长久化或网络传输, 这个时候都须要序列化和反序列化.

理由:

服务器与浏览器交互时真的没有用到 Serializable 接口吗? JSON 格局实际上就是将一个对象转化为字符串, 所以服务器与浏览器交互时的数据格式其实是字符串, 咱们来看来 String 类型的源码:

public final class String
    implements java.io.Serializable, Comparable<String>, CharSequence {
    /** The value is used for character storage. */
    private final char value[];

    /** Cache the hash code for the string */
    private int hash; // Default to 0

    /** use serialVersionUID from JDK 1.0.2 for interoperability */
    private static final long serialVersionUID = -6849794470754667710L;
    ...
}

String 类型实现了 Serializable 接口, 并显示指定 serialVersionUID 的值。

而后咱们再来看对象长久化到数据库中时的状况, Mybatis 数据库映射文件里的 insert 代码:

<insert id="insertUser" parameterType="org.tyshawn.bean.User">
        INSERT INTO t_user(name, age) VALUES (#{name}, #{age})
</insert>

实际上咱们并不是将整个对象长久化到数据库中, 而是将对象中的属性长久化到数据库中, 而这些属性都是实现了 Serializable 接口的根本属性.

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

在 Java 中实现了 Serializable 接口后, JVM 会在底层帮咱们实现序列化和反序列化, 如果咱们不实现 Serializable 接口, 那本人去写一套序列化和反序列化代码也行, 至于具体怎么写, Google 一下你就晓得了.

实现 Serializable 接口就算了, 为什么还要显示指定 serialVersionUID 的值?

如果不显示指定 serialVersionUID, JVM 在序列化时会依据属性主动生成一个 serialVersionUID, 而后与属性一起序列化, 再进行长久化或网络传输. 在反序列化时, JVM 会再依据属性主动生成一个新版 serialVersionUID, 而后将这个新版 serialVersionUID 与序列化时生成的旧版 serialVersionUID 进行比拟, 如果雷同则反序列化胜利, 否则报错.

如果显示指定了 serialVersionUID, JVM 在序列化和反序列化时依然都会生成一个 serialVersionUID, 但值为咱们显示指定的值, 这样在反序列化时新旧版本的 serialVersionUID 就统一了.

在理论开发中, 不显示指定 serialVersionUID 的状况会导致什么问题? 如果咱们的类写完后不再批改, 那当然不会有问题, 但这在理论开发中是不可能的, 咱们的类会一直迭代, 一旦类被批改了, 那旧对象反序列化就会报错. 所以在理论开发中, 咱们都会显示指定一个 serialVersionUID, 值是多少无所谓, 只有不变就行.

写个实例测试下:

(1) User 类

不显示指定 serialVersionUID.

public class User implements Serializable {
    private String name;
    private Integer age;

    public String getName() {return name;}

    public void setName(String name) {this.name = name;}

    public Integer getAge() {return age;}

    public void setAge(Integer age) {this.age = age;}

    @Override
    public String toString() {
        return "User{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}

(2) 测试类

先进行序列化, 再进行反序列化.

public class SerializableTest {private static void serialize(User user) throws Exception {ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(new File("D:\\111.txt")));
        oos.writeObject(user);
        oos.close();}

    private static User deserialize() throws Exception {ObjectInputStream ois = new ObjectInputStream(new FileInputStream(new File("D:\\111.txt")));
        return (User) ois.readObject();}

    public static void main(String[] args) throws Exception {User user = new User();
        user.setName("tyshawn");
        user.setAge(18);
        System.out.println("序列化前的后果:" + user);
        serialize(user);
        User dUser = deserialize();
        System.out.println("反序列化后的后果:" + dUser);
    }
}

(3) 后果

先正文掉反序列化代码, 执行序列化代码, 而后 User 类新增一个属性 sex

public class User implements Serializable {
    private String name;
    private Integer age;
    private String sex;

    public String getName() {return name;}

    public void setName(String name) {this.name = name;}

    public Integer getAge() {return age;}

    public void setAge(Integer age) {this.age = age;}

    public String getSex() {return sex;}

    public void setSex(String sex) {this.sex = sex;}

    @Override
    public String toString() {
        return "User{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", sex='" + sex + '\'' +
                '}';
    }
}

再正文掉序列化代码执行反序列化代码, 最初后果如下:

序列化前的后果: User{name=’tyshawn’, age=18}
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

private static final long serialVersionUID = 1L;

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

序列化前的后果: User{name=’tyshawn’, age=18}
反序列化后的后果: User{name=’tyshawn’, age=18, sex=’null’}

显示指定 serialVersionUID 后就解决了序列化与反序列化产生的 serialVersionUID 不统一的问题. 更多面试题,欢送关注 公众号 Java 面试题精选

Java 序列化的其余个性

先说论断, 被 transient 关键字润饰的属性不会被序列化, static 属性也不会被序列化.

咱们来测试下这个论断:

(1) User 类

public class User implements Serializable {
    private static final long serialVersionUID = 1L;
    private String name;
    private Integer age;
    private transient String sex;
    private static String signature = "你眼中的世界就是你本人的样子";

    public String getName() {return name;}

    public void setName(String name) {this.name = name;}

    public Integer getAge() {return age;}

    public void setAge(Integer age) {this.age = age;}

    public String getSex() {return sex;}

    public void setSex(String sex) {this.sex = sex;}

    public static String getSignature() {return signature;}

    public static void setSignature(String signature) {User.signature = signature;}

    @Override
    public String toString() {
        return "User{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", sex='" + sex + '\'' +
                ", signature='" + signature + '\'' +
                '}';
    }
}

(2) 测试类

public class SerializableTest {private static void serialize(User user) throws Exception {ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(new File("D:\\111.txt")));
        oos.writeObject(user);
        oos.close();}

    private static User deserialize() throws Exception {ObjectInputStream ois = new ObjectInputStream(new FileInputStream(new File("D:\\111.txt")));
        return (User) ois.readObject();}

    public static void main(String[] args) throws Exception {User user = new User();
        user.setName("tyshawn");
        user.setAge(18);
        user.setSex("man");
        System.out.println("序列化前的后果:" + user);
        serialize(user);
        User dUser = deserialize();
        System.out.println("反序列化后的后果:" + dUser);
    }
}

(3) 后果

先正文掉反序列化代码, 执行序列化代码, 而后批改 User 类 signature =“我的眼里只有你”, 再正文掉序列化代码执行反序列化代码, 最初后果如下:

序列化前的后果: User{name=’tyshawn’, age=18, sex=’man’, signature=’ 你眼中的世界就是你本人的样子 ’}
反序列化后的后果: User{name=’tyshawn’, age=18, sex=’null’, signature=’ 我的眼里只有你 ’}

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

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

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

正文完
 0