【Java基础第一弹】Java序列化基础篇

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 接口的类即是可序列化类,所以可序列化可以理解为序列化对象的第一步。一个类可序列化后接下来就需要进行“序列化”与“反序列化”了,这样就组成了一套完整的序列化流程,才能达到序列化对象实例的目的。

无论是采用哪种方式传输序列化内容,序列化与反序列化都可以理解为“写”和“读”操作 ,所以序列化与反序列化是共生关系,它们无法单独存在。就像读和写操作,光把内容写进文件或网络传输中,使用者却没有相应的读取手段则此“写”操作是无意义的。

序列化中所谓的“写”和“读”对应于以下方法:

1 /**
2  * 写入对象内容
3  */
4 private void writeObject(java.io.ObjectOutputStream out)
5 /**
6  * 读取对象内容
7  */
8 private void readObject(java.io.ObjectInputStream in)

通过这两个方法可以将对象实例进行“序列化”与“反序列化”操作,除了对象实例的相关操作还有很多其他类型的方法,诸如writeInt/readInt、writeBoolane/readBoolean等。

总结:

可序列化:一个类实现接口Serializable 后即变为可序列化类。

序列化:既是整套序列化流程的总称,又是单独针对某实例进行的“写”操作。

反序列化:读取已经序列化成功的实例内容,将其实例信息还原。

4.可序列化、序列化与反序列化实现

1)一个类想要序列化首先需要实现java.io.Serializable 接口:

 1 package com.animals;
 2 import java.io.Serializable;
 3 /**
 4  * 喵星人
 5  * @author 286
 6  *
 7  */
 8 public class Cat implements Serializable {
 9
10     private String name;
11     private int age;
12     private String color;
13
14     public String getName() {
15         return name;
16     }
17     public void setName(String name) {
18         this.name = name;
19     }
20     public int getAge() {
21         return age;
22     }
23     public void setAge(int age) {
24         this.age = age;
25     }
26     public String getColor() {
27         return color;
28     }
29     public void setColor(String color) {
30         this.color = color;
31     }
32 }

implements Serializable 即标识着Cat 类已经可以被序列化了。

2)存储序列化后的实例,以文件存储为例(其中os可以修改为其他形式流):

 1 Cat cat=new Cat();
 2 cat.setName("喵星人");
 3 cat.setAge(3);
 4 cat.setColor("白色");
 5 //定义文件输出流,准备将数据写入文件中
 6 OutputStream os=new FileOutputStream("E:\\cat.txt");
 7 ObjectOutputStream oos=new ObjectOutputStream(os);
 8 //写入实例数据
 9 oos.writeObject(cat);
10 oos.close();
11 os.close();

3)写入文件成功后,利用记事本打开该文件,发现文件内容根本无法识别,但其中或多或少出现了相关全限定名、属性名等信息,这些信息格式与JVM中的存储方式是一样的:

Java代码  

  1. sr com.animals.Cat@d追Y袺 I ageL colort Ljava/lang/String;L nameq ~ xp   t 鐧借
  2. 壊t  鍠垫槦浜?

将其转换成UTF-8编码后,会显示诸如:“白色”,“喵星人”等信息,但依旧大部分是乱码。然后我们再利用16进制编辑器打开此文件:

发现原来Cat 实例的相关信息其实是以固定的内容格式存储起来的而已,猜想只需要按这个格式读取的话就能恢复原实例的数据内容了吧。

4)反序列化还原实例内容:

 1 InputStream is=new FileInputStream("E:\\cat.txt");
 2 ObjectInputStream ois=new ObjectInputStream(is);
 3 //读取序列化实例内容
 4 Cat cat=(Cat)ois.readObject();
 5 System.out.println(cat.getName());
 6 System.out.println(cat.getAge());
 7 System.out.println(cat.getColor());
 8 ois.close();
 9 is.close();
10 //打印结果:
11 喵星人
12 3
13 白色

总结:

可以看出序列化的一系列操作还是非常简单实用的,通过非常简洁的代码就可以实现 实例的共享与传递。

6.序列化版本号

在平时创建可序列化类之后,一般编译器会自动提示类似如下内容:


       点击修正后弹出对话框:

其中第一个,第二个选项分别是“添加一个默认的序列化版本ID”、“生成一个序列化版本 ID”,一般情况下我们会默认选择第一种方式,此后会在可序列化类中添加一个serialVersionUID 属性:

Java代码  

  1. private static final long serialVersionUID = 1L;

serialVersionUID 就是我们要说的“序列化版本号”概念,显然我们也可以不去声明此属
性,依然可以使用序列化的各项操作来写入和获取实例信息,所以从表面现象来看添加serialVersionUID
与否并不会影响我们序列化和反序列化的使用。

其实不然,以下情况时就会凸显出serialVersionUID 的作用:

1)还是按照原来的方式序列化Cat 类实例,将实例信息存储起来。

2)此时我们修改Cat类的属性,将age去掉,换成weight,如下:

 1 package com.animals;
 2 import java.io.Serializable;
 3 /**
 4  * 喵星人
 5  * @author 286
 6  *
 7  */
 8 public class Cat implements Serializable {
 9
10     private String name;
11     private double weight;
12     private String color;
13     public String getName() {
14         return name;
15     }
16     public void setName(String name) {
17         this.name = name;
18     }
19     public double getWeight() {
20         return weight;
21     }
22     public void setWeight(double weight) {
23         this.weight = weight;
24     }
25     public String getColor() {
26         return color;
27     }
28     public void setColor(String color) {
29         this.color = color;
30     }
31
32 }

3)再利用反序列化获取实例信息:

 1 InputStream is = new FileInputStream("E:\\cat.txt");
 2 ObjectInputStream ois = new ObjectInputStream(is);
 3 Cat cat = (Cat) ois.readObject();
 4 System.out.println(cat.getName());
 5 System.out.println(cat.getWeight());
 6 System.out.println(cat.getColor());
 7 ois.close();
 8 is.close();
 9 //打印结果:
10 Exception in thread "main" java.io.InvalidClassException: com.animals.Cat; local class incompatible: stream classdesc
11
12 serialVersionUID = 1747507533076615499, local class serialVersionUID = 2870256057611081222
13     at java.io.ObjectStreamClass.initNonProxy(ObjectStreamClass.java:562)
14     at java.io.ObjectInputStream.readNonProxyDesc(ObjectInputStream.java:1583)
15     at java.io.ObjectInputStream.readClassDesc(ObjectInputStream.java:1496)
16     at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:1732)
17     at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1329)
18     at java.io.ObjectInputStream.readObject(ObjectInputStream.java:351)
19     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呢?

答案是否定的,因为在序列化过程中类的全限定名(简单理解为包名+类名)等信息已经被 存储,所以不同的类是无法反序列化兼容的。运行过程中会抛出类似如下异常:

1 Exception in thread "main" java.lang.ClassCastxception: com.animals.Cat cannot be cast to
2 com.animals.Cat2
时间: 2024-10-11 19:12:51

【Java基础第一弹】Java序列化基础篇的相关文章

java nio 第一弹

java NIO 第一弹----概览 摘要: Non-blocking I/O (usually called NIO, and sometimes called "New I/O") is a collection of Java programming language APIs that offer features for intensive I/Ooperations. It was introduced with the J2SE 1.4 release of Java b

java面试第一弹

1.  一个".java"源文件中是否可以包括多个类(不是内部类)?有什么限制?. 可以有多个类,但只能有一个public的类,并且public的类名与文件名相同 2.  Java有没有goto? Java中的保留字,但现在不在用java中使用 3.  说说&和&&的区别. &是位运算和逻辑运算,&&是用于逻辑运算,&&是用于短路的,短路就是当表达式两边第一个位false时,则不在执行第二个 4.  在JAVA中如何跳出当

Java基础の第一弹

一.虚拟机的工作机制 (1) :通过 ClassLoader 寻找和装载 class 文件 (2) :解释字节码成为指令并执行,提供 class 文件的运行环境 (3) :进行运行期间垃圾回收 (4) :提供与硬件交互的平台 二.Java 从代码到运行的全过程 1.创建类:创建Java类文件,文件名(文件后缀名为java)必须跟其中一个类名完全一致,只有该类可以带public修饰符,一个类文件中最多只有一个类被public修饰(内部类不算) 类包含如下部分:①包(package)定义,②引用(i

Java学习第一天:数据基础,打印质数的实现

第一天正式学习Java,写下这篇关于质数求解的文章,希望能更改进的更好. 首先说,以前在C上求解过质数的问题,当时没怎么在意.一直用的方法是从2开始递增到n-1,如果在这个过程中有一个数能被n整除,那么这个数就不是质数.这样做当然是没问题的最简单的一种方法. 之后看了一些文章的介绍,随着数学知识的增长,今天在学习Java语言上实现了这个想法,把这一过程记录如下: 先从最原始的递增法说起: 1.除了2之外,全部的质数是奇数,所以,循环数可以减少一般. 2.递增的界限不应是n-1,可以加以优化.对于

Java 初学 第一弹--编译并运行书上的简单程序(猜数字小游戏)

(博主原创) 首先说明一下,博主是大一上学期结束寒假时自己看的Java,然后我看的是Head First Java的中文版,因为大一学了c,所以里面的一些基本思想还是了解的,在看这本书时就浏览了一下(就是那种光看没有自己动手去敲代码的),然后看到书上的一个猜数字小游戏,就想手动敲一下,熟悉熟悉Java的语法,但是真正去做时,发现比看起来要困难一些. 首先是Java在建立一个源码文件之前要先建一个package,然后我用的Eclipse写的Java(感觉和pycharm风格差不多),再新建一个文件

java基础 第一章 对象入门

第1章 对象入门 "为什么面向对象的编程会在软件开发领域造成如此震憾的影响?" 面向对象编程(OOP)具有多方面的吸引力.对管理人员,它实现了更快和更廉价的开发与维护过程.对分析与设计人员,建模处理变得更加简单,能生成清晰.易于维护的设计方案.对程序员,对象模型显得如此高雅和浅显.此外,面向对象工具以及库的巨大威力使编程成为一项更使人愉悦的任务.每个人都可从中获益,至少表面如此. 如果说它有缺点,那就是掌握它需付出的代价.思考对象的时候,需要采用形象思维,而不是程序化的思维.与程序化设

程序猿二三事之Java基础--Java SE 5增加的特性--语法篇(一)

程序猿二三事之Java基础–Java SE 5增加的特性–语法篇(一) [ TOC ] 为什么是Java SE 5? 目前已经到了JDK-8u74了,JDK7的主版本已经于2015年4月停止公开更新. 那为什么还要来说Java/JDK5呢? Java SE在1.4(2002)趋于成熟,随着越来越多应用于开发企业应用,许多框架诞生于这个时期或走向成熟. Java SE 5.0的发布(2004)在语法层面增加了很多特性,让开发更高效,代码更整洁. 自动装箱/拆箱.泛型.注解.for循环增强.枚举.可

Java并发包下锁学习第二篇Java并发基础框架-队列同步器介绍

Java并发包下锁学习第二篇队列同步器 还记得在第一篇文章中,讲到的locks包下的类结果图吗?如下图: ? 从图中,我们可以看到AbstractQueuedSynchronizer这个类很重要(在本文中,凯哥就用AQS来代替这个类).我们先来了解这个类.对这个类了解之后,学习后面的会更容易了. 本篇是<凯哥(凯哥Java:kagejava)并发编程学习>系列之<Lock系列>教程的第一篇:<Java并发包下锁学习第二篇:队列同步器>. 本文主要内容:同步器介绍:同步器

Java基础知识二次学习-- 第一章 java基础

基础知识有时候感觉时间长似乎有点生疏,正好这几天有时间有机会,就决定重新做一轮二次学习,挑重避轻 回过头来重新整理基础知识,能收获到之前不少遗漏的,所以这一次就称作查漏补缺吧!废话不多说,开始! 第一章  JAVA简介 时间:2017年4月24日10:23:32 章节:01章_02节 内容:jdk的配置与安装 完成情况:已经完成,cmd中javac提示出相关命令 时间:2017年4月24日10:30:39 章节:01章_04节 内容:输出HelloWorld 完成情况: 已经完成 javac先将