Java 序列化和反序列化

  • 序列化和反序列化是什么?
  • 实现序列化和反序列化为什么要实现Serializable接口?
  • 实现Serializable接口后,为什么还要显式指定serialVersionUID的值?
  • 要为serialVersionUID指定个什么值?

    序列化和反序列化

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

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

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

    只在本地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.tyshawn.bean.User">
    2. INSERT INTO t_user(name, age) VALUES (#{name}, #{age})
    3. </insert>

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

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

    在Java中实现了Serializable接口后,JVM会在底层实现序列化和反序列化,如果不实现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="SmoGD"></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. }

再注释掉序列化代码执行反序列化代码,最后结果如下:
序列化前的结果: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

  1. private static final long serialVersionUID = 1L;

再执行上述步骤,测试结果如下:
序列化前的结果:User{name='tyshawn', age=18} 反序列化后的结果: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 = "我的眼里只有你",再注释掉序列化代码执行反序列化代码,最后结果如下:
序列化前的结果: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。