最近在阅读JDK源码中的集合,看到很多集合类实现了Serializable接口,Cloneable接口。在阅读了很多关于Serializable接口的博客后,浅谈下我对Serializable接口的理解。

序列化

查看 官方文档 就会发现 Serializable接口中一个成员函数或者成员变量也没有。那么这个接口的作用是什么呢。网上找了一些博客看过之后,知道这个接口的作用是实现序列化。

序列化:对象的寿命通常随着生成该对象的程序的终止而终止,有时候需要把在内存中的各种对象的状态(也就是实例变量,不是方法)保存下来,并且可以在需要时再将对象恢复。虽然你可以用你自己的各种各样的方法来保存对象的状态,但是Java给你提供一种应该比你自己的好的保存对象状态的机制,那就是序列化。

总结:Java 序列化技术可以使你将一个对象的状态写入一个Byte 流里(系列化),并且可以从其它地方把该Byte 流里的数据读出来(反序列化)。

系列化的用途

  • 想把的内存中的对象状态保存到一个文件中或者数据库中时候
  • 想把对象通过网络进行传播的时候

如何序列化

只要一个类实现Serializable接口,那么这个类就可以序列化了。

例如有一个 Person类,实现了Serializable接口,那么这个类就可以被序列化了。

class Person implements Serializable{
private static final long serialVersionUID = 1L; //一会就说这个是做什么的
String name;
int age;
public Person(String name,int age){
this.name = name;
this.age = age;
}
public String toString(){
return "name:"+name+"\tage:"+age;
}
}

通过ObjectOutputStream 的writeObject()方法把这个类的对象写到一个地方(文件),再通过ObjectInputStream 的readObject()方法把这个对象读出来。

    File file = new File("file"+File.separator+"out.txt");

    FileOutputStream fos = null;
try {
fos = new FileOutputStream(file);
ObjectOutputStream oos = null;
try {
oos = new ObjectOutputStream(fos);
Person person = new Person("tom", 22);
System.out.println(person);
oos.writeObject(person); //写入对象
oos.flush();
} catch (IOException e) {
e.printStackTrace();
}finally{
try {
oos.close();
} catch (IOException e) {
System.out.println("oos关闭失败:"+e.getMessage());
}
}
} catch (FileNotFoundException e) {
System.out.println("找不到文件:"+e.getMessage());
} finally{
try {
fos.close();
} catch (IOException e) {
System.out.println("fos关闭失败:"+e.getMessage());
}
} FileInputStream fis = null;
try {
fis = new FileInputStream(file);
ObjectInputStream ois = null;
try {
ois = new ObjectInputStream(fis);
try {
Person person = (Person)ois.readObject(); //读出对象
System.out.println(person);
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
} catch (IOException e) {
e.printStackTrace();
}finally{
try {
ois.close();
} catch (IOException e) {
System.out.println("ois关闭失败:"+e.getMessage());
}
}
} catch (FileNotFoundException e) {
System.out.println("找不到文件:"+e.getMessage());
} finally{
try {
fis.close();
} catch (IOException e) {
System.out.println("fis关闭失败:"+e.getMessage());
}
}

输出结果为:

name:tom    age:22
name:tom age:22

结果完全一样。如果我把Person类中的implements Serializable 去掉,Person类就不能序列化了。此时再运行上述程序,就会报java.io.NotSerializableException异常。

serialVersionUID

注意到上面程序中有一个 serialVersionUID ,实现了Serializable接口之后,Eclipse就会提示你增加一个 serialVersionUID,虽然不加的话上述程序依然能够正常运行。

序列化 ID 在 Eclipse 下提供了两种生成策略

  • 一个是固定的 1L
  • 一个是随机生成一个不重复的 long 类型数据(实际上是使用 JDK 工具,根据类名、接口名、成员方法及属性等来生成)

上面程序中,输出对象和读入对象使用的是同一个Person类。

如果是通过网络传输的话,如果Person类的serialVersionUID不一致,那么反序列化就不能正常进行。例如在客户端A中Person类的serialVersionUID=1L,而在客户端B中Person类的serialVersionUID=2L 那么就不能重构这个Person对象。

客户端A中的Person类:

class Person implements Serializable{   

    private static final long serialVersionUID = 1L;

    String name;
int age; public Person(String name,int age){
this.name = name;
this.age = age;
}
public String toString(){
return "name:"+name+"\tage:"+age;
}
}

客户端B中的Person类:

class Person implements Serializable{   

    private static final long serialVersionUID = 2L;

    String name;
int age; public Person(String name,int age){
this.name = name;
this.age = age;
}
public String toString(){
return "name:"+name+"\tage:"+age;
}
}

试图重构就会报java.io.InvalidClassException异常,因为这两个类的版本不一致,local class incompatible,重构就会出现错误。

如果没有特殊需求的话,使用用默认的 1L 就可以,这样可以确保代码一致时反序列化成功。那么随机生成的序列化 ID 有什么作用呢,有些时候,通过改变序列化 ID 可以用来限制某些用户的使用。

静态变量序列化

串行化只能保存对象的非静态成员交量,不能保存任何的成员方法和静态的成员变量,而且串行化保存的只是变量的值,对于变量的任何修饰符都不能保存。

如果把Person类中的name定义为static类型的话,试图重构,就不能得到原来的值,只能得到null。说明对静态成员变量值是不保存的。这其实比较容易理解,序列化保存的是对象的状态,静态变量属于类的状态,因此 序列化并不保存静态变量。

transient关键字

经常在实现了 Serializable接口的类中能看见transient关键字。这个关键字并不常见。 transient关键字的作用是:阻止实例中那些用此关键字声明的变量持久化;当对象被反序列化时(从源文件读取字节序列进行重构),这样的实例变量值不会被持久化和恢复。

当某些变量不想被序列化,同是又不适合使用static关键字声明,那么此时就需要用transient关键字来声明该变量。

例如用 transient关键字 修饰name变量

class Person implements Serializable{   

    private static final long serialVersionUID = 1L;

    transient String name;
int age; public Person(String name,int age){
this.name = name;
this.age = age;
}
public String toString(){
return "name:"+name+"\tage:"+age;
}
}

在反序列化视图重构对象的时候,作用与static变量一样: 输出结果为:

name:null   age:22

在被反序列化后,transient 变量的值被设为初始值,如 int 型的是 0,对象型的是 null。

注:对于某些类型的属性,其状态是瞬时的,这样的属性是无法保存其状态的。例如一个线程属性或需要访问IO、本地资源、网络资源等的属性,对于这些字段,我们必须用transient关键字标明,否则编译器将报措。

序列化中的继承问题

  • 当一个父类实现序列化,子类自动实现序列化,不需要显式实现Serializable接口。
  • 一个子类实现了 Serializable 接口,它的父类都没有实现 Serializable 接口,要想将父类对象也序列化,就需要让父类也实现Serializable 接口。

第二种情况中:如果父类不实现 Serializable接口的话,就需要有默认的无参的构造函数。这是因为一个 Java 对象的构造必须先有父对象,才有子对象,反序列化也不例外。在反序列化时,为了构造父对象,只能调用父类的无参构造函数作为默认的父对象。因此当我们取父对象的变量值时,它的值是调用父类无参构造函数后的值。在这种情况下,在序列化时根据需要在父类无参构造函数中对变量进行初始化,否则的话,父类变量值都是默认声明的值,如 int 型的默认是 0,string 型的默认是 null。

例如:

class People{
int num;
public People(){} //默认的无参构造函数,没有进行初始化
public People(int num){ //有参构造函数
this.num = num;
}
public String toString(){
return "num:"+num;
}
}
class Person extends People implements Serializable{ private static final long serialVersionUID = 1L; String name;
int age; public Person(int num,String name,int age){
super(num); //调用父类中的构造函数
this.name = name;
this.age = age;
}
public String toString(){
return super.toString()+"\tname:"+name+"\tage:"+age;
}
}

在一端写出对象的时候

Person person = new Person(10,"tom", 22); //调用带参数的构造函数num=10,name = "tim",age =22

System.out.println(person);

oos.writeObject(person); //写出对象

在另一端读出对象的时候

Person person = (Person)ois.readObject(); //反序列化,调用父类中的无参构函数。

System.out.println(person);

输出为

    num:0   name:tom    age:22

发现由于父类中无参构造函数并没有对num初始化,所以num使用默认值为0。

总结

序列化给我们提供了一种技术,用于保存对象的变量。以便于传输。虽然也可以使用别的一些方法实现同样的功能,但是java给我们提供的方法使用起来是非常方便的。以上仅仅是我的一些理解,由于本人水平有限,不足之处还请指正。下篇学习Cloneable接口的用途。

原文地址 http://blademastercoder.github.io/2015/01/29/java-Serializable.html

Serializable(转)的更多相关文章

  1. 对 Serializable和Parcelable理解

    1.首先他们两个接口都是为了实现对象的序列化,使之可以传递,所谓序列化就是将对象信息装换成可以存储的介质的过程. 2.Serializable是jdk所提供的序列化接口,该接口存在于io包下,可想用于 ...

  2. java.io.Serializable 序列化接口

    什么是序列化.反序列化? Serialization(序列化)是一种将对象以一连串的字节描述的过程: 反序列化deserialization是一种将这些字节重建成一个对象的过程. 序列化通俗一点说就是 ...

  3. serialVersionUID, ObjectInputStream与ObjectOutputStream类,Serializable接口,serialVersionUID的作用和用法

    ObjectInputStream与ObjectOutputStream类所读写的对象必须实现Serializable接口,对象中的transient和static类型成员变量不会被读取和写入 Ser ...

  4. Java Serializable系列化与反系列化

    [引言] 将 Java 对象序列化为二进制文件的 Java 序列化技术是 Java 系列技术中一个较为重要的技术点,在大部分情况下,开发人员只需要了解被序列化的类需要实现 Serializable 接 ...

  5. 在Activity之间传递参数(三)——serializable和parcelable的区别

    传递值对象: 一.serializable实现:简单易用 serializable的迷人之处在于你只需要对某个类以及它的属性实现Serializable 接口即可.Serializable 接口是一种 ...

  6. Java中的Serializable接口transient关键字,及字节、字符、对象IO

    1.什么是序列化和反序列化Serialization是一种将对象转为为字节流的过程:deserialization是将字节流恢复为对象的过程. 2.什么情况下需要序列化a)当你想把的内存中的对象保存到 ...

  7. Hibernate的实体类为什么要实现Serializable序列化接口?

    Hibernate的实体类中为什么要继承Serializable?   hibernate有二级缓存,缓存会将对象写进硬盘,就必须序列化,以及兼容对象在网络中的传输 等等. java中常见的几个类(如 ...

  8. Java 序列化Serializable详解

    Java 序列化Serializable详解(附详细例子) Java 序列化Serializable详解(附详细例子) 1.什么是序列化和反序列化Serialization(序列化)是一种将对象以一连 ...

  9. Serializable unordered set

    Serializable unordered set 可序列化哈希set #include <boost/algorithm/string/predicate.hpp> #include ...

  10. Java里Serializable的那些事

    本文为原创文章,欢迎转载,但请注明出处http://www.cnblogs.com/yexiubiao/p/5014015.html,未在文章页面明显位置给出原文连接的,将保留追究法律责任的权利. 通 ...

随机推荐

  1. poj2318(叉积判断点在直线左右+二分)

    题目链接:https://vjudge.net/problem/POJ-2318 题意:有n条线将矩形分成n+1块,m个点落在矩形内,求每一块点的个数. 思路: 最近开始肝计算几何,之前的几何题基本处 ...

  2. Android Stadio导入Android工程项目,只有Edit Configurations的解决办法

    这几天导入老的Android工程项目,导入后,也不发红也不报错,但是Run这一项没有可运行的App,就只有一个Edit Configurations.经过查询后,发现运行一下Sync Project ...

  3. Linux下安装jdk中遇到的坑

    比如:我以jdk-8u211-linux-i586.tar.gz为例进行. 下载完成后解压到指定文件下先创建java文件目录,如果已存在就不用创建[root@lyh:] # mkdir -p /usr ...

  4. PAT题目AC汇总(待补全)

    题目AC汇总 甲级AC PAT A1001 A+B Format (20 分) PAT A1002 A+B for Polynomials(25) PAT A1005 Spell It Right ( ...

  5. kube-dns和coreDNS的使用

    内部服务发现 前面我们给大家讲解了 Service 的用法,我们可以通过 Service 生成的 ClusterIP(VIP)来访问 Pod 提供的服务,但是在使用的时候还有一个问题:我们怎么知道某个 ...

  6. vue学习【一、开发环境搭建】

    一.安装node.js https://nodejs.org/en/ 建议安装LTS版本 安装完毕之后cmd命令查看node版本,如果不识别,记住设置环境变量 显示上面信息则安装成功 二.设置node ...

  7. C# 读取本地图片

    /// <summary> /// 通过FileStream 来打开文件,这样就可以实现不锁定Image文件,到时可以让多用户同时访问Image文件 /// </summary> ...

  8. nginx触屏版跟PC的代理设置

    server { listen ; set $mobile_rewrite do_not_perform; if ( $http_user_agent ~* "(android|bb\d+| ...

  9. vue模板字符串写法

    1.模板字符串拼接id <div class="thumbnail" :id="`ctrol_${item.id}`"> <i :class= ...

  10. 微信小程序wxs如何使用

    新建一个.wxs文件 <!-- 引入.wxs文件 src为相对路径,module指定当前模块的名称 --> <wxs module="filter" src=&q ...