Java类通过实现java.io.Serializable 接口便可启用其序列化功能。实现了序列化的类的实例可以在不同的系统或JVM间传递,并且不会丢失原实例的相关状态及值。
为一个类开启序列化功能只需实现Serializable 接口即可,仅仅作为使用者可以不必了解其内部的更深层次的实现及流程,但是如果想要更好的使用序列化功能就需要与我一起详细的学习和了解它。
1.什么是Java序列化
Java类通过实现java.io.Serializable 接口以启用其序列化功能:
import java.io.Serializable; public class Cat implements Serializable { }
未实现此接口(java.io.Serializable)的类将无法使其任何状态序列化或反序列化。可序列化类的所有子类型本身都是可序列化的,也就是说实现了Serializable 接口的类的子类默认也是可序列化的。
序列化接口java.io.Serializable 没有任何方法或字段,仅用于标识可序列化的语义,Serializable 接口可以理解为一种标志,实现了它就被标识为可序列化。
2.序列化的用途
实现序列化接口Serializable 的类,在被实例化后,该类就具备了一定的状态和相应属性值,在相同的虚拟机进程中,我们可以通过参数传递的方式让别的方法或类来使用它,从而获取该实例的相关信息。但是一旦该JVM进程关闭或该实例不再被使用,该实例的数据内容要么被GC清理掉,要么直接随JVM进程关闭而消失。
如果我想要将该实例对象内容保存或传递给其他虚拟机使用该怎么办呢?
答案当然不是实现 java.io.Serializable 接口,或者说不仅仅是实现java.io.Serializable 接口。
java.io.Serializable 接口只是一个标识,标识着实现它的类可以被序列化,之后再通过其他手段才能将该类的实例信息进行存储和传递。
所以序列化的一个用途就是持久化存储,也就是将对象实例直接存储到相关文件中;另一个用途就是不同系统间进行传递。后者用途更为广泛。
总结:
序列化的用途:传递和存储。
3.可序列化、序列化与反序列化
实现Serializable 接口的类即是可序列化类,所以可序列化可以理解为序列化对象的第一步。一个类可序列化后接下来就需要进行“序列化”与“反序列化”了,这样就组成了一套完整的序列化流程,才能达到序列化对象实例的目的。
无论是采用哪种方式传输序列化内容,序列化与反序列化都可以理解为“写”和“读”操作 ,所以序列化与反序列化是共生关系,它们无法单独存在。就像读和写操作,光把内容写进文件或网络传输中,使用者却没有相应的读取手段则此“写”操作是无意义的。
序列化中所谓的“写”和“读”对应于以下方法:
/** * 写入对象内容 */ private void writeObject(java.io.ObjectOutputStream out) /** * 读取对象内容 */ private void readObject(java.io.ObjectInputStream in)
通过这两个方法可以将对象实例进行“序列化”与“反序列化”操作,除了对象实例的相关操作还有很多其他类型的方法,诸如writeInt/readInt、writeBoolane/readBoolean等。
总结:
可序列化:一个类实现接口Serializable 后即变为可序列化类。
序列化:既是整套序列化流程的总称,又是单独针对某实例进行的“写”操作。
反序列化:读取已经序列化成功的实例内容,将其实例信息还原。
4.可序列化、序列化与反序列化实现
1)一个类想要序列化首先需要实现java.io.Serializable 接口:
package com.animals; import java.io.Serializable; /** * 喵星人 * @author 286 * */ public class Cat implements Serializable { private String name; private int age; private String color; public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } public String getColor() { return color; } public void setColor(String color) { this.color = color; } }
implements Serializable 即标识着Cat 类已经可以被序列化了。
2)存储序列化后的实例,以文件存储为例(其中os可以修改为其他形式流):
Cat cat=new Cat(); cat.setName("喵星人"); cat.setAge(3); cat.setColor("白色"); //定义文件输出流,准备将数据写入文件中 OutputStream os=new FileOutputStream("E:\\cat.txt"); ObjectOutputStream oos=new ObjectOutputStream(os); //写入实例数据 oos.writeObject(cat); oos.close(); os.close();
3)写入文件成功后,利用记事本打开该文件,发现文件内容根本无法识别,但其中或多或少出现了相关全限定名、属性名等信息,这些信息格式与JVM中的存储方式是一样的:
sr com.animals.Cat@d追Y袺 I ageL colort Ljava/lang/String;L nameq ~ xp t 鐧借 壊t 鍠垫槦浜?
将其转换成UTF-8编码后,会显示诸如:“白色”,“喵星人”等信息,但依旧大部分是乱码。然后我们再利用16进制编辑器打开此文件:
发现原来Cat 实例的相关信息其实是以固定的内容格式存储起来的而已,猜想只需要按这个格式读取的话就能恢复原实例的数据内容了吧。
4)反序列化还原实例内容:
InputStream is=new FileInputStream("E:\\cat.txt"); ObjectInputStream ois=new ObjectInputStream(is); //读取序列化实例内容 Cat cat=(Cat)ois.readObject(); System.out.println(cat.getName()); System.out.println(cat.getAge()); System.out.println(cat.getColor()); ois.close(); is.close(); //打印结果: 喵星人 3 白色
总结:
可以看出序列化的一系列操作还是非常简单实用的,通过非常简洁的代码就可以实现 实例的共享与传递。
6.序列化版本号
在平时创建可序列化类之后,一般编译器会自动提示类似如下内容:
点击修正后弹出对话框:
其中第一个,第二个选项分别是“添加一个默认的序列化版本ID”、“生成一个序列化版本 ID”,一般情况下我们会默认选择第一种方式,此后会在可序列化类中添加一个serialVersionUID 属性:
private static final long serialVersionUID = 1L;
serialVersionUID 就是我们要说的“序列化版本号”概念,显然我们也可以不去声明此属 性,依然可以使用序列化的各项操作来写入和获取实例信息,所以从表面现象来看添加serialVersionUID 与否并不会影响我们序列化和反序列化的使用。
其实不然,以下情况时就会凸显出serialVersionUID 的作用:
1)还是按照原来的方式序列化Cat 类实例,将实例信息存储起来。
2)此时我们修改Cat类的属性,将age去掉,换成weight,如下:
package com.animals; import java.io.Serializable; /** * 喵星人 * @author 286 * */ public class Cat implements Serializable { private String name; private double weight; private String color; public String getName() { return name; } public void setName(String name) { this.name = name; } public double getWeight() { return weight; } public void setWeight(double weight) { this.weight = weight; } public String getColor() { return color; } public void setColor(String color) { this.color = color; } }
3)再利用反序列化获取实例信息:
InputStream is = new FileInputStream("E:\\cat.txt"); ObjectInputStream ois = new ObjectInputStream(is); Cat cat = (Cat) ois.readObject(); System.out.println(cat.getName()); System.out.println(cat.getWeight()); System.out.println(cat.getColor()); ois.close(); is.close(); //打印结果: Exception in thread "main" java.io.InvalidClassException: com.animals.Cat; local class incompatible: stream classdesc serialVersionUID = 1747507533076615499, local class serialVersionUID = 2870256057611081222 at java.io.ObjectStreamClass.initNonProxy(ObjectStreamClass.java:562) at java.io.ObjectInputStream.readNonProxyDesc(ObjectInputStream.java:1583) at java.io.ObjectInputStream.readClassDesc(ObjectInputStream.java:1496) at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:1732) at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1329) at java.io.ObjectInputStream.readObject(ObjectInputStream.java:351) at Test.main(Test.java:17)
4)结果却反序列化失败,抛出了异常,大致意思是两个类的serialVersionUID 值不相等。
序列化运行时使用serialVersionUID 版本号与每个可序列化类相关联,该序列号在反序列 化过程中用于验证序列化对象的发送者和接收者是否为该对象加载了与序列化兼容的类。如果接收者加载的该对象的类的 serialVersionUID 与对应的发送者的类的版本号不同,则反序列化将会导致 InvalidClassException异常。可序列化类可以通过声明名为 "serialVersionUID" 的字段(该字段必须是静态 (static)、最终(final) 的 long 型字段)显式声明其自己的 serialVersion ANY-ACCESS-MODIFIER (任意修饰符) static final long serialVersionUID = 1L;
如果可序列化类未显式声明 serialVersionUID,则序列化运行时将基于该类的各个方面计 算该类的默认 serialVersionUID 值,如“Java(TM) 对象序列化规范”中所述。不过,强烈建议所有可序列化类都显式声明 serialVersionUID 值,原因是计算默认的 serialVersionUID 对类的详细信息具有较高的敏感性,根据编译器实现的不同可能千差万别,这样 在反序列化过程中可能会导致意外的 InvalidClassException。因此,为保证 serialVersionUID 值跨不同 java 编译器实现的一致性,序列 化类必须声明一个明确的 serialVersionUID 值。还强烈建议使用 private 修饰符显示声明 serialVersionUID(如果可能),原因是这种声 明仅应用于直接声明类 -- serialVersionUID 字段作为继承成员没有用处。数组类不能声明一个明确的 serialVersionUID,因此它们总是具有默认的计算值,但是数组类没有匹配 serialVersionUID 值的要求。
总结:
serialVersionUID 的作用类似与我们从网络上下载文件时MD5值的作用,MD5消息摘 要值可以判断所下载的文件是否被篡改过。serialVersionUID 的作用也是如此,用于判断序列化与反序列化的类是否相同或兼容,所谓兼容就 是即使类的内部属性不相同,但只要serialVersionUID 相同,类还是那个类就可以反序列化。更形象的比喻一下就是“即使你整容了,那么你还是你”。
疑问:
如果除了类名不同,所有属性和serialVersionUID 都相同的话,那么Cat类实例被序 列化后我能否直接反序列化成Cat2呢?
答案是否定的,因为在序列化过程中类的全限定名(简单理解为包名+类名)等信息已经被 存储,所以不同的类是无法反序列化兼容的。运行过程中会抛出类似如下异常:
Exception in thread "main" java.lang.ClassCastxception: com.animals.Cat cannot be cast to com.animals.Cat2
下一篇:Java序列化进阶篇
相关推荐
本文将深入探讨Hessian框架的基础知识,它是一个高效的二进制序列化协议,广泛应用于Java和.NET之间跨语言通信。通过学习Hessian,我们可以更有效地处理数据传输,提高应用性能。 首先,让我们理解什么是序列化。...
本篇将介绍几个常用的Java序列化和反序列化框架,并通过示例代码进行演示。 1. **Java标准序列化**: Java标准序列化通过实现`Serializable`接口来标记类可序列化。要序列化一个对象,可以使用`ObjectOutputStream...
Java序列化是将对象转换为字节流的过程,目的是为了保存对象的状态以便稍后恢复或传输到其他地方。通过实现`Serializable`接口,一个Java对象就可以被序列化。这个接口是一个标记接口,没有定义任何方法,仅表示对象...
Java 面试题基础篇是面向Java开发人员的一份详细的面试题库,涵盖了 Java 的基础知识点,包括面向对象编程、变量作用域、事件委托机制、垃圾回收机制、序列化、排序算法、继承、多态、内部类、Collection 框架等。...
除了标准的Java序列化,还有许多第三方库提供了更高效、更安全的序列化方案,如Google的Protocol Buffers、Apache Avro和Jackson的Json序列化。这些库通常提供更紧凑的数据格式,更快的序列化速度,并且支持跨语言的...
Java程序设计是软件开发领域中的核心技能之一,尤其对于初学者而言,掌握基础篇和进阶篇的内容至关重要。本资源提供了“Java语言程序设计 第10版”的基础篇和进阶篇答案,帮助学习者深化对Java编程的理解,解决学习...
本篇将深入探讨如何通过Socket传递Java对象,重点讲解对象序列化的过程。 首先,让我们理解什么是Socket。Socket在计算机网络中是一种进程间通信机制,它允许两个网络应用程序之间建立连接并交换数据。在Java中,`...
最后,基础篇可能还会涉及输入/输出流(I/O Stream)的基础知识,包括文件读写、对象序列化等,这对于任何实际项目都至关重要。 通过阅读《Java技术教程.基础篇》PDF,学习者可以全面掌握Java编程的基本技能,为...
最后,Java的反射机制允许在运行时动态地获取类的信息并调用其方法,这对于插件系统、序列化、动态代理等高级应用场景非常有用。同时,Java还提供了强大的泛型特性,增强了类型安全,简化了代码。 总结起来,"JAVA...
【标题】"Java面试题基础篇共3页.pdf" 是一份专门为准备Java面试者设计的资料,涵盖了Java编程语言的基础知识和常见面试问题。这份资料可能是由经验丰富的开发者或者技术面试官整理,旨在帮助求职者巩固核心概念,...
根据提供的文件信息,“Java语言程序设计-基础篇(原书第8版)”这本书主要面向初学者,旨在通过详细的讲解和示例帮助读者掌握Java编程的基础知识。以下是对该书籍可能涉及的一些关键知识点进行的总结: ### 1. Java...
《Java工程师成神之路(基础篇)》是阿里云开发者社区历经五年精心打造的一部Java学习宝典,旨在为Java初学者提供一个全面且深入的知识框架。这本书详细讲解了Java编程语言的基础概念、语法和核心特性,是每一位志在...
Java语言程序设计的基础篇中,第十九章主要探讨了二进制输入/输出(I/O)的概念和使用。在Java中,I/O操作涉及到文件的读取和写入,这对于任何程序设计都至关重要。本章主要涵盖了以下几个核心知识点: 1. **Java中的...
Java技术大全基础篇是针对初学者的一份详细教程,旨在帮助读者掌握Java编程语言的基础概念和核心特性。在本教程中,你将深入学习到以下几个关键知识点: 1. **Java环境搭建**:首先,你需要安装Java Development ...
《JAVA语言程序设计》第八版是Java编程领域中一本经典的教材,它分为基础篇和进阶篇,旨在全面深入地教授读者如何使用Java语言进行软件开发。这份压缩包包含了该书配套的答案代码,对于学习者来说是一份极其宝贵的...
Java程序员必读基础篇是一个专为初学者设计的教程,旨在帮助他们建立坚实的Java编程基础。这个教程可能包含了从入门到进阶的各种知识点,涵盖了语言语法、类与对象、异常处理、集合框架、多线程、输入/输出(I/O)以及...
Java语言程序设计基础篇是学习Java编程的入门教材,第8版提供了丰富的学习材料和练习题,旨在帮助初学者掌握编程基础。这份压缩包包含了该书的英文版部分课后习题答案,特别是编程题的解答,对于自我检验和深入理解...