JAVA对象序列化及TODO标签

java 对象序列化

当两个进程在进行远程通信时,彼此可以发送各种类型的数据。无论是何种类型的数据,都会以二进制序列的形式在网络上传送。发送方需要把这个Java对象转换为字节序列,才能在网络上传送;接收方则需要把字节序列再恢复为Java对象。

  把Java对象转换为字节序列的过程称为对象的序列化。

  把字节序列恢复为Java对象的过程称为对象的反序列化。

  对象的序列化主要有两种用途:

  1) 把对象的字节序列永久地保存到硬盘上,通常存放在一个文件中;

2) 在网络上传送对象的字节序列。

 java.io.ObjectOutputStream代表对象输出流,它的writeObject(Object obj)方法可对参数指定的obj对象进行序列化,把得到的字节序列写到一个目标输出流中。

  java.io.ObjectInputStream代表对象输入流,它的readObject()方法从一个源输入流中读取字节序列,再把它们反序列化为一个对象,并将其返回。、

  只有实现了Serializable和Externalizable接口的类的对象才能被序列化。Externalizable接口继承自Serializable接口,实现Externalizable接口的类完全由自身来控制序列化的行为,而仅实现Serializable接口的类可以采用默认的序列化方式 。

  对象序列化包括如下步骤:

  1) 创建一个对象输出流,它可以包装一个其他类型的目标输出流,如文件输出流;

  2) 通过对象输出流的writeObject()方法写对象。

  对象反序列化的步骤如下:

  1) 创建一个对象输入流,它可以包装一个其他类型的源输入流,如文件输入流;

  2) 通过对象输入流的readObject()方法读取对象。

简单来说序列化就是一种用来处理对象流的机制,所谓对象流也就是将对象的内容进行流化,流的概念这里不用多说(就是I/O),我们可以对流化后的对象进行读写操作,也可将流化后的对象传输于网络之间(注:要想将对象传输于网络必须进行流化)!在对对象流进行读写操作时会引发一些问题,而序列化机制正是用来解决这些问题的!

问题的引出:

如上所述,读写对象会有什么问题呢?比如:我要将对象写入一个磁盘文件而后再将其读出来会有什么问题吗?别急,其中一个最大的问题就是对象引用!举个例子来说:假如我有两个类,分别是A和B,B类中含有一个指向A类对象的引用,现在我们对两个类进行实例化{ A a = new A(); B b = new B(); },这时在内存中实际上分配了两个空间,一个存储对象a,一个存储对象b,接下来我们想将它们写入到磁盘的一个文件中去,就在写入文件时出现了问题!因为对象b包含对对象a的引用,所以系统会自动的将a的数据复制一份到b中,这样的话当我们从文件中恢复对象时(也就是重新加载到内存中)时,内存分配了三个空间,而对象a同时在内存中存在两份,想一想后果吧,如果我想修改对象a的数据的话,那不是还要搜索它的每一份拷贝来达到对象数据的一致性,这不是我们所希望的!

以下序列化机制的解决方案:

1.保存到磁盘的所有对象都获得一个序列号(1, 2, 3等等)

2.当要保存一个对象时,先检查该对象是否被保存了。

3.如果以前保存过,只需写入"与已经保存的具有序列号x的对象相同"的标记,否则,保存该对象

通过以上的步骤序列化机制解决了对象引用的问题!

序列化的实现:

将需要被序列化的类实现Serializable接口,该接口没有需要实现的方法,implements Serializable只是为了标注该对象是可被序列化的,然后使用一个输出流(如:FileOutputStream)来构造一个 ObjectOutputStream(对象流)对象,接着,使用ObjectOutputStream对象的writeObject(Object obj)方法就可以将参数为obj的对象写出(即保存其状态),要恢复的话则用输入流。

ObjectOutputStream只能对Serializable接口的类的对象进行序列化。默认情况下,ObjectOutputStream按照默认方式序列化,这种序列化方式仅仅对对象的非transient的实例变量进行序列化,而不会序列化对象的transient的实例变量,也不会序列化静态变量。

当ObjectOutputStream对一个Customer对象进行序列化时,如果该对象具有writeObject()方法,那么就会执行这一方法,否则就按默认方式序列化。在该对象的writeObjectt()方法中,可以先调用ObjectOutputStream的defaultWriteObject()方法,使得对象输出流先执行默认的序列化操作。同理可得出反序列化的情况,不过这次是defaultReadObject()方法。

  有些对象中包含一些敏感信息,这些信息不宜对外公开。如果按照默认方式对它们序列化,那么它们的序列化数据在网络上传输时,可能会被不法份子窃取。对于这类信息,可以对它们进行加密后再序列化,在反序列化时则需要解密,再恢复为原来的信息。

  默认的序列化方式会序列化整个对象图,这需要递归遍历对象图。如果对象图很复杂,递归遍历操作需要消耗很多的空间和时间,它的内部数据结构为双向列表。

  在应用时,如果对某些成员变量都改为transient类型,将节省空间和时间,提高序列化的性能。

Externalizable接口继承自Serializable接口,如果一个类实现了Externalizable接口,那么将完全由这个类控制自身的序列化行为。Externalizable接口声明了两个方法:

public void writeExternal(ObjectOutput out) throws IOException

public void readExternal(ObjectInput in) throws IOException , ClassNotFoundException

  前者负责序列化操作,后者负责反序列化操作。

  在对实现了Externalizable接口的类的对象进行反序列化时,会先调用类的不带参数的构造方法,这是有别于默认反序列方式的。如果把类的不带参数的构造方法删除,或者把该构造方法的访问权限设置为private、默认或protected级别,会抛出java.io.InvalidException: no valid constructor异常。

修改默认的序列化机制:

在序列化的过程中,有些数据字段我们不想将其序列化,对于此类字段我们只需要在定义时给它加上transient关键字即可,对于transient字段序列化机制会跳过不会将其写入文件,当然也不可被恢复。但有时我们想将某一字段序列化,但它在SDK中的定义却是不可序列化的类型,这样的话我们也必须把他标注为transient,可是不能写入又怎么恢复呢?好在序列化机制为包含这种特殊问题的类提供了如下的方法定义:

private void readObject(ObjectInputStream in) throws

IOException, ClassNotFoundException;

private void writeObject(ObjectOutputStream out) throws

IOException;

(注:这些方法定义时必须是私有的,因为不需要你显示调用,序列化机制会自动调用的)

使用以上方法我们可以手动对那些你又想序列化又不可以被序列化的数据字段进行写出和读入操作。

总结目标:

将对象保存到磁盘中,或允许在网络中直接传输对象,对象序列化机制允许把内存中的java对象转换成与平台无关的二进制流,从而允许把这种二进制流持久保存在磁盘上,或通过网络将这种二进制流传输到另一个网络节点。

含义:

对象的序列化(Serialize)指将一个java对象写入IO流中,与此对应的是,对象的反序列化(Deserialize)则指的是从IO流中恢复该java对象。

方法:

为了让某个类是可序列化的,该类必须实现如下两个接口之一:

Serializable

Externalizable

Java的很多类已经实现了Serializable,该接口是一个标记接口,实现该接口无须实现任何方法,它只是表明该类的实例是可序列化的。

所有可能在网络上传输的对象的类都应该是可序列化的,否则程序会出现异常。比如RMI(Remote Method Invoke 远程方法调用,Java EE的基础)过程中的参数和返回值;所有需要保存到磁盘里的对象都必须可序列化。

因为序列化时RMI过程的参数和返回值都必须实现的机制,而RMI又是javaEE技术的基础:所有分布式应用常常需要跨平台、跨网络,因此要求所有传递的参数、返回值必须实现序列化,因此序列化机制是javaEE平台的基础。通常建议:程序创建的每个JavaBean类都实现Serializable。

需要注意的地方:

1. 反序列化机制无须通过构造器来初始化java对象。

2. 如果我们向文件中使用序列化机制写入了多个java对象,使用反序列化机制恢复对象时必须按照实际写入的顺序读取。

3. 如果一个可序列化类有多个父类(直接或间接父类),则该类的所有父类要么是可序列化的,要么有无参的构造函数---否则反序列化时会抛出InvalidClassException异常。当程序创建子类实例时,系统会隐式地为它的所有父类都创建实例(并建立和此子类的关联)。当我们反序列化某个子类的实例时,反序列化机制需要恢复其关联的父类实例。

恢复这些父类实例有两种方法:

(1) 使用反序列化机制

(2) 使用父类无参构造函数

以上两种方式,反序列化机制优先选择第一种机制。如果某个父类既不可序列化(不能使用第一种方法),又没有提供无参的构造函数(不能用第二种方法),则反序列化该子类实例时会抛出异常。、

4. 如果某个类的属性类型不是基本类型或String类型,而是另一个引用类型,则这个引用类必须是可序列化的,否则拥有该类型属性的类是不可序列化的。

 四. 可序列化类的不同版本的序列化兼容性

  凡是实现Serializable接口的类都有一个表示序列化版本标识符的静态变量:

private static final long serialVersionUID;

  以上serialVersionUID的取值是Java运行时环境根据类的内部细节自动生成的。如果对类的源代码作了修改,再重新编译,新生成的类文件的serialVersionUID的取值有可能也会发生变化。

  类的serialVersionUID的默认值完全依赖于Java编译器的实现,对于同一个类,用不同的Java编译器编译,有可能会导致不同的serialVersionUID,也有可能相同。为了提高哦啊serialVersionUID的独立性和确定性,强烈建议在一个可序列化类中显示的定义serialVersionUID,为它赋予明确的值。显式地定义serialVersionUID有两种用途:

  1) 在某些场合,希望类的不同版本对序列化兼容,因此需要确保类的不同版本具有相同的serialVersionUID;

2) 在某些场合,不希望类的不同版本对序列化兼容,因此需要确保类的不同版本具有不同的serialVersionUID。

serialVersionUID: 字面意思上是序列化的版本号,这个在刚刚接触java编程时,学序列化大家一般都不会注意到,在你一个类序列化后除非你强制去掉了myeclipse中warning的功能,在你实现序列化的类上会有这个警告,点击会出现增加这个版本号。

说说这个版本号得作用:就是确保了不同版本之间的兼容性,不仅能够向前兼容,还能够向后兼容,即在版本升级时反序列化仍保持对象的唯一性。

它有两种生成方式:

一个是默认的1L,比如:private static final long serialVersionUID = 1L;

一个是根据类名、接口名、成员方法及属性等来生成一个64位的哈希字段,比如: private static final long serialVersionUID = xxxxL;

从两个例子上来说明这个序列化号的作用:

这是一个类实现了序列化,但是并没有显式的声明序列号,在这里说明一下,如果没有显式声明序列号,那么在程序编译时会自己生成这个版本序列号,

程序1

public class Animal implements Serializable{

public String name;

public String no;

 

}

 

通过这个程序来将上面的一个实体存起来:程序2

public static void main(String[] args) throws Exception{

Animal an = new Animal();

 FileOutputStream f = new FileOutputStream("foo");

 ObjectOutputStream oos = new ObjectOutputStream(f);

oos.writeObject(an);

 oos.close();

}

通过这个程序读取出刚才存储的类:程序3

public static void main(String[] args) throws Exception{

FileInputStream fis = new FileInputStream("foo");

ObjectInputStream ois = new ObjectInputStream(fis);

 

Animal ani = (Animal)ois.readObject();

ois.close();

fis.close();

}

这种编译是成功的,但是当你在程序1中的类增加一个成员变量的时候,在运行程序3,就会报错:

Exception in thread "main" java.io.InvalidClassException: koal.Animal;……

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 koal.SerialVersion.main(SerialVersion.java:22)

这是为什么呢?

因为在你没有显式的声明序列号时,在程序编译时候会自动生成一个序列号,存储在文件中,但是在你更改了实体类的时候又会重新生成一个序列号,在程序运行的时候,Java的序列化机制是通过在运行时判断类的serialVersionUID来验证版本一致性的。在进行反序列化时,JVM会把传来的字节流中的serialVersionUID与本地相应实体(类)的serialVersionUID进行比较,如果相同就认为是一致的,可以进行反序列化,否则就会出现序列化版本不一致的异常。也就是说,在你读取这个object的时候,他会比较你的现在这个类的序列号和你存储的那个序列号是否相等,如果相等,则说明这是同一个版本,如果不相等则是不同版本,不同版本之间的成员变量是不相同的,所以就会报错,但是当你显式的声明了这个序列号时,不论你如何修改类中的成员变量还是方法,都不会引起版本之间不兼容得问题,这个就加强了你的程序的健壮性。

在很多地方我们都会用到这个序列化,例如在游戏中,游戏升级之后,还要能够让程序成功读取以前的数据,让升级之后的程序还能认识以前的数据文件,即使数据格式不一致,也不会丢失数据。

像一些持久化的配置文件,中途要升级,增加属性,都要使用这个版本序列号,在这里我们是希望程序中只要是实现序列化的类都最好是显式的声明这个序列版本号。

Findbugs关于对象序列化相关报错:

.Class is Serializable, but doesn't define serialVersionUID

serialVersionUID 用来表明类的不同版本间的兼容性

简单来说,Java的序列化机制是通过在运行时判断类的serialVersionUID来验证版本一致性的。在进行反序列化时,JVM会把传来的字节流中的serialVersionUID与本地 相应实体(类)的serialVersionUID进行比较,如果相同就认为是一致的,可以进行反序列化,否则就会出现序列化版本不一致的异常。

当实现java.io.Serializable接口的实体(类)没有显式地定义一个名为serialVersionUID,类型为long的变量时,Java序列化机制会根据编译的class自动生成一个 serialVersionUID作序列化版本比较用,这种情况下,只有同一次编译生成的class才会生成相同的serialVersionUID 。

如果我们不希望通过编译来强制划分软件版本,即实现序列化接口的实体能够兼容先前版本,未作更改的类,就需要显式地定义一个名为serialVersionUID,类型为long 的变量,不修改这个变量值的序列化实体都可以相互进行串行化和反串行化。

也就是这个错误 你要定义一个名为 serialVersionUID,类型为long的变量 按照新版Eclipse自动填写规则 就是:

private static final long serialVersionUID = 1L;

Non-transient non-serializable instance field in serializable class

This Serializable class defines a non-primitive instance field which is neither transient, Serializable,

or java.lang.Object, and does not appear to implement the Externalizable interface or the readObject()

and writeObject() methods.? Objects of this class will not be deserialized correctly if a non-Serializable object

is stored in this field.

这个错误的意思是:在可序列化的类中存在不能序列化或者不能暂存的数据

Myeclipse中ToDO标签使用

开发过程中遇到将来要做的许可证限制问题,如果单单是注释很容易忘掉,搜集各种资料后觉得还是MyEclipse的TOTO标签好些。

今天知道了,这个算是一个标注。比如你现在不想做,想过会做,但是又怕忘了这事或忘记在哪个代码里了。你就可以在你的代码里加上:

Java代码 

  1. try {

  2. list = friService.getFriends(24010L, null, page, true);

  3. } catch (Exception e) {

  4. // TODO 要做异常发生时的处理

  5. e.printStackTrace();

  6. }

try {

list = friService.getFriends(24010L, null, page, true);

} catch (Exception e) {

// TODO 要做异常发生时的处理

e.printStackTrace();

}

然后单击[window — show View — Tasks ] 打开任务视图 就会发现有这么一段
JAVA对象序列化及TODO标签_第1张图片

然后直接点击这行就会到那段 TODO注释啦


你可能感兴趣的:(JAVA对象序列化及TODO标签)