最近在阅读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. DISCO Presents Discovery Channel Code Contest 2020 Qual Task E. Majority of Balls

    Not able to solve this problem during the contest (virtual participation). The first observation is ...

  2. python并发编程之多进程(实践篇)

    一 multiprocessing模块介绍 python中的多线程无法利用多核优势,如果想要充分地使用多核CPU的资源,在python中大部分情况需要使用多进程.Python提供了multiproce ...

  3. 一块40克的砝码,摔成4块,利用天平,刚好可以称出1~40g所有整数克,问:这4块分别是多少克

    public static void main(String[] args) { List<Integer> list = new ArrayList<>();//记录每组数的 ...

  4. PB做的托盘程序(最小化后在左下角显示图标)

    见‘文件’资源

  5. php 处理数字为金钱格式

    number_format(需要转换的数字,保留小数个数,小数点符号,每三位的分隔符) echo number_format("1000000")."<br> ...

  6. Java集合--Hash、Hash冲突

    一.Hash 散列表(Hash table,也叫哈希表),是根据键(Key)而直接访问在内存存储位置的数据结构.也就是说,它通过计算一个关于键值的函数,将所需查询的数据映射到表中一个位置来访问记录,这 ...

  7. python+django学习三

    在这个网站看https://sshwsfc.github.io/xadmin/     xadmin结果一堆的坑,文档找不到界面,dome登陆就报错permission denied for rela ...

  8. Centos7 部署.net core2.1 详细步骤

    安装dotnet sdk(添加产品秘钥与yum源) 添加yum源:sudo rpm -Uvh https://packages.microsoft.com/config/rhel/7/packages ...

  9. java Map 四种遍历方法

    public static void main(String[] args) { Map<String, String> map = new HashMap<String, Stri ...

  10. Outline 科学的上网

    outline 官网:https://getoutline.org/zh-CN/home 下载 Outline 管理器 下载 Outline 客户端 配置浏览器代理