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。