Java对象的序列化

  下面将介绍对象的序列化——一种将对象转成字节方便传送到别处或存储在硬盘上,并且再从转化成的字节重构对象的机制。

  序列化是分布式管理必备的工具,分布式处理中将对象从一个虚拟传到另一个虚拟机。序列化也被用于故障转移和负载均衡方面,序列化对象可以从一个服务器移到另一个服务器。如果你开发过服务器端软件,就会经常需要序列化。下面介绍如何序列化。(摘自 《Core Java》)

一、简单的一个例子

 1 package serializable;
 2 
 3 import java.io.Serializable;
 4 
 5 /**
 6  * @author zsh
 7  * @company wlgzs
 8  * @create 2019-03-05 18:22
 9  * @Describe 序列化实体类
10  */
11 
12 public class Person implements Serializable {
13     
14     private String name;
15     private Integer age;
16     private String sex;
17 
18     public Person(String name, Integer age, String sex) {
19         this.name = name;
20         this.age = age;
21         this.sex = sex;
22     }
23 
24     @Override
25     public String toString() {
26         return "Person{" +
27                 "name='" + name + '\'' +
28                 ", age=" + age +
29                 ", sex='" + sex + '\'' +
30                 '}';
31     }
32 }
 1 package serializable;
 2 
 3 import java.io.*;
 4 
 5 /**
 6  * @author zsh
 7  * @company wlgzs
 8  * @create 2019-03-05 18:24
 9  * @Describe 序列化测试类
10  */
11 public class Main1 {
12 
13     /**
14      * 对象的序列化操作
15      * @throws IOException
16      */
17     static void SerializePerson() throws IOException {
18         Person person = new Person("zsh",21,"男");
19         //ObjectOutputStream 对象输出流,将Person对象储存在指定路径下的data.txt文件中,完成对Person对象的序列化
20         ObjectOutputStream oo = new ObjectOutputStream(new FileOutputStream(new File(System.getProperty("user.dir")+"\\src\\serializable\\data.txt")));
21         oo.writeObject(person);
22         System.out.println("对象序列化成功");
23         oo.close();
24     }
25 
26     /**
27      * 对象的反序列化操作
28      * @return Person对象
29      * @throws IOException
30      * @throws ClassNotFoundException
31      */
32     static Person DeserializePerson() throws IOException, ClassNotFoundException {
33         ObjectInputStream ois = new ObjectInputStream(new FileInputStream(new File(System.getProperty("user.dir")+"\\src\\serializable\\data.txt")));
34         Person person = (Person) ois.readObject();
35         System.out.println("对象反序列化成功");
36         ois.close();
37         return person;
38     }
39 
40     public static void main(String[] args) throws IOException, ClassNotFoundException {
41         SerializePerson();
42         System.out.println(DeserializePerson());
43     }
44 }

运行结果:

  我们可以先注释掉 main 方法里的反序列化的那两行代码,先执行序列化。然后就可以在指定目录,看到一个刚才创建的 data.txt 文件。使用记事本打开文件

1 ¬í sr serializable.Personhó̇“d¯ L aget Ljava/lang/Integer;L namet Ljava/lang/String;L sexq ~ xpsr java.lang.Integerâ ¤÷‡8 I valuexr java.lang.Number†¬•”à‹  xp   t zsht 男

  然后注释掉序列化对象部分,运行反序列化部分。

运行结果:

二、为什么要手动设置 serialVersionUID

  通常我们有时候在 Person 类里不写

private static final long serialVersionUID = 1L;

  也能正常序列化和反序列化。

  因为系统会自带帮我们创建一个 serialVersionUID。

  下面测试一个例子,不设置 serialVersionUID ,当对象信息改变的时候,会出现什么状况。

   1、先把序列化的那行注释掉,不进行序列化操作。使用刚才生成的 data.txt

  2、在 Person 里添加一个属性 phone

  3、运行反序列化,会报一个异常

Exception in thread "main" java.io.InvalidClassException: serializable.Person; local class incompatible: stream classdesc serialVersionUID = 7562613079521060015, local class serialVersionUID = 293166062145411448
    at java.io.ObjectStreamClass.initNonProxy(ObjectStreamClass.java:699)
    at java.io.ObjectInputStream.readNonProxyDesc(ObjectInputStream.java:1885)
    at java.io.ObjectInputStream.readClassDesc(ObjectInputStream.java:1751)
    at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:2042)
    at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1573)
    at java.io.ObjectInputStream.readObject(ObjectInputStream.java:431)
    at serializable.Main1.DeserializePerson(Main1.java:34)
    at serializable.Main1.main(Main1.java:42)

  因为在序列化的时候,将对象写入文件的时候,会写入类名和所有实例变量的名称和值。

  其中 serialVersionUID 因为没有设置默认值,系统会自动根据哈希值生成一个。如果类的实现发生改变,那么 serialVersionUID 也会发生改变。

  相反,如果我们在序列化之前加上

private static final long serialVersionUID = 1L;

  然后序列化,然后给 Person 类加上一个 phone 字段。

  这时候就不会报异常了。

  至于 serialVersioLnUID 等于几并不重要,但是该属性的修饰和类型必须为 final long。

三、使用 transient 标记不需要序列化的字段

  有些实例变量是不需要序列化的——例如当一个对象保留缓存值的时候,一般也不需要序列化该缓存值,重新计算缓存值而不是存储缓存值可能更好。

  为了实现某些实例变量不序列化,简单的方法就是给这个变量添加一个 transient 修饰符,打过 transient 标记的字段在序列化的时候就会背忽略。

 1 package serializable;
 2 
 3 import java.io.Serializable;
 4 
 5 /**
 6  * @author zsh
 7  * @company wlgzs
 8  * @create 2019-03-05 18:22
 9  * @Describe 序列化实体类
10  */
11 
12 public class Person implements Serializable {
13 
14     private static final long serialVersionUID = 1L;
15 
16     private String name;
17     private Integer age;
18     private String sex;
19     private transient String phone;
20 
21     public Person(String name, Integer age, String sex, String phone) {
22         this.name = name;
23         this.age = age;
24         this.sex = sex;
25         this.phone = phone;
26     }
27 
28     @Override
29     public String toString() {
30         return "Person{" +
31                 "name='" + name + '\'' +
32                 ", age=" + age +
33                 ", sex='" + sex + '\'' +
34                 ", phone='" + phone + '\'' +
35                 '}';
36     }
37 }

 运行结果:

  其中 phone 是没有被序列化的,所有反序列化的时候也是没有值的。因为phone 是 String 类型的,默认是 null。

posted @ 2019-03-05 19:20  梨花梦蝶  阅读(262)  评论(0编辑  收藏  举报