赞
踩
接着来聊聊Serializable,Serializable的意思是序列化。
1. 序列化的概念
序列化 (Serialization)将对象的状态信息转换为可以存储或传输的形式的过程。在序列化期间,对象将其当前状态写入到临时或持久性存储区。以后,可以通过从存储区中读取或反序列化对象的状态,重新创建该对象。
2. 序列化的使用场景
RMI是Java中的概念:远程方法调用(Remote Method Invocation),能够让在客户端Java虚拟机上的对象像调用本地对象一样调用服务端java虚拟机中的对象上的方法。
3. 序列化的过程
在没有序列化前,每个保存在堆(Heap)中的对象都有相应的状态(state),即实例变量比如:
- public static void main(String[] args) {
-
- Coder coder = new Coder();
- coder.name = "kuang";
- coder.age = 27;
- coder.lang = "java";
- }
-
- private static class Coder implements Serializable {
-
- private static final long serialVersionUID = -7245589157910452589L;
-
- String name;
- int age;
- String lang;
- }
当通过下面的代码序列化之后,coder对象中的实例变量的值都被保存到coder.ser文件中,这样以后又可以把它从文件中读出来,重新在堆中创建原来的对象。当然保存时候不仅仅是保存对象的实例变量的值,JVM还要保存一些小量信息,比如类的类型等以便恢复原来的对象。
- public static void main(String[] args) {
-
- Coder coder = new Coder();
- coder.name = "kuang";
- coder.age = 27;
- coder.lang = "java";
- try {
- FileOutputStream fs = new FileOutputStream("./coder.ser");
- ObjectOutputStream os = new ObjectOutputStream(fs);
- os.writeObject(coder);
- } catch (IOException e) {
- e.printStackTrace();
- }
- }
看看生成的coder.ser文件:
- aced 0005 7372 002f 696f 2e6b 7a77 2e61
- 6476 616e 6365 2e63 7364 6e5f 626c 6f67
- 2e54 6573 7453 6572 6961 6c69 7a61 626c
- 6524 436f 6465 729b 727f 0cb6 5366 9302
- 0003 4900 0361 6765 4c00 046c 616e 6774
- 0012 4c6a 6176 612f 6c61 6e67 2f53 7472
- 696e 673b 4c00 046e 616d 6571 007e 0001
- 7870 0000 001b 7400 046a 6176 6174 0005
- 6b75 616e 67
以十六进制的方式存储,具体不深入剖析,里面包含了序列化号,类的信息(本身和父类),变量类型,变量值等,和Java的字节码一个道理。
4. 反序列化过程
-
- try {
- ObjectInputStream ois = new ObjectInputStream(new FileInputStream(new File("./coder.ser")));
- Coder coder1 = (Coder) ois.readObject();
- System.out.println("deserialize coder = " + coder1.name + " " + coder1.age + " " + coder1.lang);
- } catch (IOException e) {
- e.printStackTrace();
- } catch (ClassNotFoundException e) {
- e.printStackTrace();
- }
执行输出:
deserialize coder = kuang 27 java
5. 关于serialVersionUID
序列化操作的时候系统会把当前类的serialVersionUID写入到序列化文件中,当反序列化时系统会去检测文件中的serialVersionUID,判断它是否与当前类的serialVersionUID一致,如果一致就说明序列化类的版本与当前类版本是一样的,可以反序列化成功,否则失败。
serialVersionUID有两种显示的生成方式:
如果我序列化成功了一个对象,然后更改它的serialVersionUID,反序列化时会报错,比如上面的例子,serialVersionUID改成7245589157910452599:
- java.io.InvalidClassException: io.kzw.advance.csdn_blog.TestSerializable$Coder; local class incompatible: stream classdesc serialVersionUID = -7245589157910452599, local class serialVersionUID = -7245589157910452689
- at java.io.ObjectStreamClass.initNonProxy(ObjectStreamClass.java:616)
- at java.io.ObjectInputStream.readNonProxyDesc(ObjectInputStream.java:1630)
- at java.io.ObjectInputStream.readClassDesc(ObjectInputStream.java:1521)
- at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:1781)
- at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1353)
- at java.io.ObjectInputStream.readObject(ObjectInputStream.java:373)
- at io.kzw.advance.csdn_blog.TestSerializable.main(TestSerializable.java:24)
6. 序列化后能修改类吗?
修改之后需要重新进行序列化,但是其实是可以的,如果你能接收反序列化的对象拿不到修改的属性值。
- private static class Coder implements Serializable {
-
- private static final long serialVersionUID = -7245589157910452689L;
-
- String name;
- int age;
- String lang;
- int sex;
- }
上面给Coder类新增了一个sex属性,反序列化后拿不到sex的值,因为之前序列化的对象没有这个属性。
7. 序列化前和序列化后的对象的关系
是==还是equal? 是浅复制还是深复制?
答案是深复制,反序列化还原后的对象地址与原来的的地址不同。
8. 静态变量能否序列化?
不能。
- private static class Coder implements Serializable {
-
- private static final long serialVersionUID = -7245589157910452689L;
-
- static int years;
-
- String name;
- int age;
- String lang;
- int sex;
- }
给上面的Coder类加一个静态属性years。
然后序列化:
- Coder coder = new Coder();
- coder.name = "kuang";
- coder.age = 27;
- coder.lang = "java";
- coder.sex = 1;
- Coder.years = 6;
- try {
- FileOutputStream fs = new FileOutputStream("./coder.ser");
- ObjectOutputStream os = new ObjectOutputStream(fs);
- os.writeObject(coder);
- } catch (IOException e) {
- e.printStackTrace();
- }
再看看反序列化:
- try {
- ObjectInputStream ois = new ObjectInputStream(new FileInputStream(new File("./coder.ser")));
- Coder coder1 = (Coder) ois.readObject();
- System.out.println("deserialize coder = " + coder1.name + " " + coder1.age + " " + coder1.lang
- + " " + coder1.sex + " " + coder1.years);
- } catch (IOException e) {
- e.printStackTrace();
- } catch (ClassNotFoundException e) {
- e.printStackTrace();
- }
执行输出:
deserialize coder = kuang 27 java 1 0
序列化会忽略静态变量,即序列化不保存静态变量的状态。静态成员属于类级别的,所以不能序列化,即序列化的是对象的状态不是类的状态。这里的不能序列化的意思是序列化信息中不包含这个静态成员域。
9. transient
Java中transient关键字的作用就是让某些被修饰的成员属性变量不被序列化。
- private static class Coder implements Serializable {
-
- private static final long serialVersionUID = -7245589157910452689L;
-
- static int years;
-
- transient String name;
- int age;
- String lang;
- int sex;
- }
执行反序列化:
deserialize coder = null 27 java 1 0
比如在android中,不同页面间传递的数据时,支持的类型包括基本类型和String,还有Serializable对象和Parcelable。如果Serializable的某个对象中的某个属性被transient修饰了,那么接收数据的地方时拿不到这个属性的值的。
10. 对象包含其他类型对象时
- public static void main(String[] args) {
-
- Coder coder = new Coder();
- coder.name = "kuang";
- coder.age = 27;
- coder.lang = "java";
- coder.sex = 1;
- Coder.years = 6;
- GF gf = new GF();
- gf.name = "unknow";
- coder.gf = gf;
- try {
- FileOutputStream fs = new FileOutputStream("./coder.ser");
- ObjectOutputStream os = new ObjectOutputStream(fs);
- os.writeObject(coder);
- } catch (IOException e) {
- e.printStackTrace();
- }
- }
-
- private static class Coder implements Serializable {
-
- private static final long serialVersionUID = -7245589157910452689L;
-
- static int years;
-
- transient String name;
- int age;
- String lang;
- int sex;
-
- GF gf;
- }
-
- private static class GF {
-
- String name;
- }
上面的GF类没有序列化,所以对Coder对象进行序列化时会报错:
java.io.NotSerializableException: io.kzw.advance.csdn_blog.TestSerializable$GF
必须让GF类也实现Serializable接口:
- private static class GF implements Serializable {
-
- private static final long serialVersionUID = -9136411921026672520L;
-
- String name;
- }
所以一个对象要想被成功序列化和反序列化,它包含的自定义类必须也是可序列化的。String类自身实现了Serializable接口。
11. 单例模式中的readResolve()
单例模式是为了运行时,只创建一个该类的对象。
熟悉单例模式的同学都知道有这么一种写法:
- public static class CoderInstance implements Serializable {
-
- private static final long serialVersionUID = 19856593275581391L;
-
- private static class InstanceHolder {
- private static final CoderInstance instatnce = new CoderInstance("kuang", 27);
- }
-
- public static CoderInstance getInstance() {
- return InstanceHolder.instatnce;
- }
-
- private String name;
-
- private int age;
-
- private CoderInstance() {
- System.out.println("none-arg constructor");
- }
-
- private CoderInstance(String name, int age) {
- System.out.println("arg constructor");
- this.name = name;
- this.age = age;
- }
-
- private Object readResolve() throws ObjectStreamException {
- return InstanceHolder.instatnce;
- }
- }
那么为什么要加入readResolve()这个方法呢?
无论是实现Serializable接口,或是Externalizable接口,当从I/O流中读取对象时,readResolve()方法都会被调用到。实际上就是用readResolve()中返回的对象直接替换在反序列化过程中创建的对象,而被创建的对象则会被垃圾回收掉。
这样才能保证单例对象的唯一性。
最后,Serializable是需要走IO的,所以性能可想而知,不过它非常简单方便,正确使用还是能满足需求的。android中的Parcelable也是一个序列化实现,是在内存中做数据传递用的,性能比Serializable高出很多,不过编写维护起来很费劲。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。