当前位置:   article > 正文

Java篇 - 聊聊Serializable (常见问题集锦)_java serializable 时间参数报错

java serializable 时间参数报错

接着来聊聊Serializable,Serializable的意思是序列化。

 

1. 序列化的概念

序列化 (Serialization)将对象的状态信息转换为可以存储或传输的形式的过程。在序列化期间,对象将其当前状态写入到临时或持久性存储区。以后,可以通过从存储区中读取或反序列化对象的状态,重新创建该对象。

 

 

2. 序列化的使用场景

  • 当你想把内存中的对象状态保存到一个文件中或者数据库中时候。
  • 当你想用套接字在网络上传送对象的时候。
  • 当你想通过RMI传输对象的时候。

RMI是Java中的概念:远程方法调用(Remote Method Invocation),能够让在客户端Java虚拟机上的对象像调用本地对象一样调用服务端java虚拟机中的对象上的方法。

 

 

3. 序列化的过程

在没有序列化前,每个保存在堆(Heap)中的对象都有相应的状态(state),即实例变量比如:
 

  1. public static void main(String[] args) {
  2. Coder coder = new Coder();
  3. coder.name = "kuang";
  4. coder.age = 27;
  5. coder.lang = "java";
  6. }
  7. private static class Coder implements Serializable {
  8. private static final long serialVersionUID = -7245589157910452589L;
  9. String name;
  10. int age;
  11. String lang;
  12. }

当通过下面的代码序列化之后,coder对象中的实例变量的值都被保存到coder.ser文件中,这样以后又可以把它从文件中读出来,重新在堆中创建原来的对象。当然保存时候不仅仅是保存对象的实例变量的值,JVM还要保存一些小量信息,比如类的类型等以便恢复原来的对象。

  1. public static void main(String[] args) {
  2. Coder coder = new Coder();
  3. coder.name = "kuang";
  4. coder.age = 27;
  5. coder.lang = "java";
  6. try {
  7. FileOutputStream fs = new FileOutputStream("./coder.ser");
  8. ObjectOutputStream os = new ObjectOutputStream(fs);
  9. os.writeObject(coder);
  10. } catch (IOException e) {
  11. e.printStackTrace();
  12. }
  13. }

看看生成的coder.ser文件:

  1. aced 0005 7372 002f 696f 2e6b 7a77 2e61
  2. 6476 616e 6365 2e63 7364 6e5f 626c 6f67
  3. 2e54 6573 7453 6572 6961 6c69 7a61 626c
  4. 6524 436f 6465 729b 727f 0cb6 5366 9302
  5. 0003 4900 0361 6765 4c00 046c 616e 6774
  6. 0012 4c6a 6176 612f 6c61 6e67 2f53 7472
  7. 696e 673b 4c00 046e 616d 6571 007e 0001
  8. 7870 0000 001b 7400 046a 6176 6174 0005
  9. 6b75 616e 67

以十六进制的方式存储,具体不深入剖析,里面包含了序列化号,类的信息(本身和父类),变量类型,变量值等,和Java的字节码一个道理。

 

 

4. 反序列化过程

  1. try {
  2. ObjectInputStream ois = new ObjectInputStream(new FileInputStream(new File("./coder.ser")));
  3. Coder coder1 = (Coder) ois.readObject();
  4. System.out.println("deserialize coder = " + coder1.name + " " + coder1.age + " " + coder1.lang);
  5. } catch (IOException e) {
  6. e.printStackTrace();
  7. } catch (ClassNotFoundException e) {
  8. e.printStackTrace();
  9. }

执行输出:

deserialize coder = kuang 27 java

 

 

5. 关于serialVersionUID

序列化操作的时候系统会把当前类的serialVersionUID写入到序列化文件中,当反序列化时系统会去检测文件中的serialVersionUID,判断它是否与当前类的serialVersionUID一致,如果一致就说明序列化类的版本与当前类版本是一样的,可以反序列化成功,否则失败。

serialVersionUID有两种显示的生成方式:        

  • 一是默认的1L,比如:private static final long serialVersionUID = 1L;        
  • 二是根据类名、接口名、成员方法及属性等来生成一个64位的哈希字段,一般IDE可以帮助你生成。

如果我序列化成功了一个对象,然后更改它的serialVersionUID,反序列化时会报错,比如上面的例子,serialVersionUID改成7245589157910452599:

  1. java.io.InvalidClassException: io.kzw.advance.csdn_blog.TestSerializable$Coder; local class incompatible: stream classdesc serialVersionUID = -7245589157910452599, local class serialVersionUID = -7245589157910452689
  2. at java.io.ObjectStreamClass.initNonProxy(ObjectStreamClass.java:616)
  3. at java.io.ObjectInputStream.readNonProxyDesc(ObjectInputStream.java:1630)
  4. at java.io.ObjectInputStream.readClassDesc(ObjectInputStream.java:1521)
  5. at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:1781)
  6. at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1353)
  7. at java.io.ObjectInputStream.readObject(ObjectInputStream.java:373)
  8. at io.kzw.advance.csdn_blog.TestSerializable.main(TestSerializable.java:24)

 

 

6. 序列化后能修改类吗?

修改之后需要重新进行序列化,但是其实是可以的,如果你能接收反序列化的对象拿不到修改的属性值。

  1. private static class Coder implements Serializable {
  2. private static final long serialVersionUID = -7245589157910452689L;
  3. String name;
  4. int age;
  5. String lang;
  6. int sex;
  7. }

上面给Coder类新增了一个sex属性,反序列化后拿不到sex的值,因为之前序列化的对象没有这个属性。

 

 

7. 序列化前和序列化后的对象的关系

是==还是equal? 是浅复制还是深复制?

答案是深复制,反序列化还原后的对象地址与原来的的地址不同。

 

 

8. 静态变量能否序列化?

不能。

  1. private static class Coder implements Serializable {
  2. private static final long serialVersionUID = -7245589157910452689L;
  3. static int years;
  4. String name;
  5. int age;
  6. String lang;
  7. int sex;
  8. }

 给上面的Coder类加一个静态属性years。

 

然后序列化:

  1. Coder coder = new Coder();
  2. coder.name = "kuang";
  3. coder.age = 27;
  4. coder.lang = "java";
  5. coder.sex = 1;
  6. Coder.years = 6;
  7. try {
  8. FileOutputStream fs = new FileOutputStream("./coder.ser");
  9. ObjectOutputStream os = new ObjectOutputStream(fs);
  10. os.writeObject(coder);
  11. } catch (IOException e) {
  12. e.printStackTrace();
  13. }

 

再看看反序列化:

  1. try {
  2. ObjectInputStream ois = new ObjectInputStream(new FileInputStream(new File("./coder.ser")));
  3. Coder coder1 = (Coder) ois.readObject();
  4. System.out.println("deserialize coder = " + coder1.name + " " + coder1.age + " " + coder1.lang
  5. + " " + coder1.sex + " " + coder1.years);
  6. } catch (IOException e) {
  7. e.printStackTrace();
  8. } catch (ClassNotFoundException e) {
  9. e.printStackTrace();
  10. }

执行输出:

deserialize coder = kuang 27 java 1 0
 

序列化会忽略静态变量,即序列化不保存静态变量的状态。静态成员属于类级别的,所以不能序列化,即序列化的是对象的状态不是类的状态。这里的不能序列化的意思是序列化信息中不包含这个静态成员域。

 

 

9transient

Java中transient关键字的作用就是让某些被修饰的成员属性变量不被序列化。

  1. private static class Coder implements Serializable {
  2. private static final long serialVersionUID = -7245589157910452689L;
  3. static int years;
  4. transient String name;
  5. int age;
  6. String lang;
  7. int sex;
  8. }

执行反序列化:

deserialize coder = null 27 java 1 0

比如在android中,不同页面间传递的数据时,支持的类型包括基本类型和String,还有Serializable对象和Parcelable。如果Serializable的某个对象中的某个属性被transient修饰了,那么接收数据的地方时拿不到这个属性的值的。

 

 

10. 对象包含其他类型对象时

  1. public static void main(String[] args) {
  2. Coder coder = new Coder();
  3. coder.name = "kuang";
  4. coder.age = 27;
  5. coder.lang = "java";
  6. coder.sex = 1;
  7. Coder.years = 6;
  8. GF gf = new GF();
  9. gf.name = "unknow";
  10. coder.gf = gf;
  11. try {
  12. FileOutputStream fs = new FileOutputStream("./coder.ser");
  13. ObjectOutputStream os = new ObjectOutputStream(fs);
  14. os.writeObject(coder);
  15. } catch (IOException e) {
  16. e.printStackTrace();
  17. }
  18. }
  19. private static class Coder implements Serializable {
  20. private static final long serialVersionUID = -7245589157910452689L;
  21. static int years;
  22. transient String name;
  23. int age;
  24. String lang;
  25. int sex;
  26. GF gf;
  27. }
  28. private static class GF {
  29. String name;
  30. }

上面的GF类没有序列化,所以对Coder对象进行序列化时会报错:

java.io.NotSerializableException: io.kzw.advance.csdn_blog.TestSerializable$GF

 

必须让GF类也实现Serializable接口:

  1. private static class GF implements Serializable {
  2. private static final long serialVersionUID = -9136411921026672520L;
  3. String name;
  4. }

所以一个对象要想被成功序列化和反序列化,它包含的自定义类必须也是可序列化的。String类自身实现了Serializable接口。

 

 

11. 单例模式中的readResolve()

单例模式是为了运行时,只创建一个该类的对象。

熟悉单例模式的同学都知道有这么一种写法:

  1. public static class CoderInstance implements Serializable {
  2. private static final long serialVersionUID = 19856593275581391L;
  3. private static class InstanceHolder {
  4. private static final CoderInstance instatnce = new CoderInstance("kuang", 27);
  5. }
  6. public static CoderInstance getInstance() {
  7. return InstanceHolder.instatnce;
  8. }
  9. private String name;
  10. private int age;
  11. private CoderInstance() {
  12. System.out.println("none-arg constructor");
  13. }
  14. private CoderInstance(String name, int age) {
  15. System.out.println("arg constructor");
  16. this.name = name;
  17. this.age = age;
  18. }
  19. private Object readResolve() throws ObjectStreamException {
  20. return InstanceHolder.instatnce;
  21. }
  22. }

那么为什么要加入readResolve()这个方法呢?

无论是实现Serializable接口,或是Externalizable接口,当从I/O流中读取对象时,readResolve()方法都会被调用到。实际上就是用readResolve()中返回的对象直接替换在反序列化过程中创建的对象,而被创建的对象则会被垃圾回收掉。

这样才能保证单例对象的唯一性。

 

 

最后,Serializable是需要走IO的,所以性能可想而知,不过它非常简单方便,正确使用还是能满足需求的。android中的Parcelable也是一个序列化实现,是在内存中做数据传递用的,性能比Serializable高出很多,不过编写维护起来很费劲。

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/我家小花儿/article/detail/543071
推荐阅读
相关标签
  

闽ICP备14008679号