java 序列化 serializable 接口--详解!

为什么JAVA要实现对象序列化 
简单来说序列化就是一 种用来处理对象流的机制,所谓对象流也就是将对象的内容进行流化,流的概念这里不用多说(就是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的对象写出(即保存其状态),要恢复的话则用输入流。

修改默认的序列化机制

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

private void readObject(ObjectInputStream in) throws

IOException, ClassNotFoundException;

private void writeObject(ObjectOutputStream out) throws

IOException;

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

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

下面是一个典型的例子,java.awt.geom包中的Point2D.Double类就是不可序列化的,因为该类没有实现Serializable接口,在我的例子中将把它当作LabeledPoint类中的一个数据字段,并演示如何将其序列化!


view plaincopy to clipboardprint?
import java.io.*;   
import java.awt.geom.*;   
public class TransientTest   
{   
public static void main(String[] args)   
{   
LabeledPoint label = new LabeledPoint("Book", 5.00, 5.00);   
try  
{   
System.out.println(label);//写入前   
ObjectOutputStream out = new ObjectOutputStream(new  
FileOutputStream("Label.txt"));   
out.writeObject(label);   
out.close();   
  
System.out.println(label);//写入后   
  
ObjectInputStream in = new ObjectInputStream(new  
FileInputStream("Label.txt"));   
LabeledPoint label1 = (LabeledPoint)in.readObject();   
in.close();   
System.out.println(label1);//读出并加1.0后   
}   
catch (Exception e)   
{   
e.printStackTrace();   
}   
}   
  
}   
class LabeledPoint implements Serializable   
{   
public LabeledPoint(String str, double x, double y)   
{   
label = str;   
point = new Point2D.Double(x, y);   
}   
  
private void writeObject(ObjectOutputStream out) throws IOException   
{   
/**  
*必须通过调用defaultWriteObject()方法来写入  
*对象的描述以及那些可以被序列化的字段  
*/  
out.defaultWriteObject();   
out.writeDouble(point.getX());   
out.writeDouble(point.getY());   
}   
  
private void readObject(ObjectInputStream in)   
throws IOException, ClassNotFoundException   
{   
/**  
*必须调用defaultReadObject()方法  
*/  
in.defaultReadObject();   
double x = in.readDouble() + 1.0;   
double y = in.readDouble() + 1.0;   
point = new Point2D.Double(x, y);   
}   
  
public String toString()   
{   
return getClass().getName()   
+ "[label = "+ label   
+ ", point.getX() = "+ point.getX()   
+ ", point.getY() = "+ point.getY()   
+ "]";   
}   
  
private  String label;   
transient private Point2D.Double point;   
}  
import java.io.*;
import java.awt.geom.*;
public class TransientTest
{
public static void main(String[] args)
{
LabeledPoint label = new LabeledPoint("Book", 5.00, 5.00);
try
{
System.out.println(label);//写入前
ObjectOutputStream out = new ObjectOutputStream(new
FileOutputStream("Label.txt"));
out.writeObject(label);
out.close();

System.out.println(label);//写入后

ObjectInputStream in = new ObjectInputStream(new
FileInputStream("Label.txt"));
LabeledPoint label1 = (LabeledPoint)in.readObject();
in.close();
System.out.println(label1);//读出并加1.0后
}
catch (Exception e)
{
e.printStackTrace();
}
}

}
class LabeledPoint implements Serializable
{
public LabeledPoint(String str, double x, double y)
{
label = str;
point = new Point2D.Double(x, y);
}

private void writeObject(ObjectOutputStream out) throws IOException
{
/**
*必须通过调用defaultWriteObject()方法来写入
*对象的描述以及那些可以被序列化的字段
*/
out.defaultWriteObject();
out.writeDouble(point.getX());
out.writeDouble(point.getY());
}

private void readObject(ObjectInputStream in)
throws IOException, ClassNotFoundException
{
/**
*必须调用defaultReadObject()方法
*/
in.defaultReadObject();
double x = in.readDouble() + 1.0;
double y = in.readDouble() + 1.0;
point = new Point2D.Double(x, y);
}

public String toString()
{
return getClass().getName()
+ "[label = "+ label
+ ", point.getX() = "+ point.getX()
+ ", point.getY() = "+ point.getY()
+ "]";
}

private  String label;
transient private Point2D.Double point;
}
 

为什么不是所有的类都序列化?

很重要的一个原因就是为了安全,java的类安全机制是做的很好的.
对于一个你要传输的对象,比如写到文件,或者进行rmi传输等等,在传输的过程中,
这个对象的private等域是不受保护的.
     还有就是一些资源分配的问题,比如thread,序列化是很难对他重新分配资源,
所以并非所有的类都可以序列化.

有哪位大侠深入浅出的讲一件深层次的东西。



------------------------------------------------------------------------------!!

最近在阅读Core J2EE Patterns 的时候发现例子里用于在各个层次里进行传输的TO(Data Transfer Object)都实现了java.io.Serializable接口,看到这些偶突然感到茅塞顿开~困扰了很久的关于Serializable的疑问渐渐解开了,查找相关资料并总结如下:

序列化是什么:
序列化就是将一个对象的状态(各个属性量)保存起来,然后在适当的时候再获得。
序列化分为两大部分:序列化和反序列化。序列化是这个过程的第一部分,将数据分解成字节流,以便存储在文件中或在网络上传输。反序列化就是打开字节流并重构对象。对象序列化不仅要将基本数据类型转换成字节表示,有时还要恢复数据。恢复数据要求有恢复数据的对象实例



如果某个类能够被序列化,其子类也可以被序列化。声明为static和transient类型的成员数据不能被序列化。因为static代表类的状态, transient代表对象的临时数据。

什么时候使用序列化
一:对象序列化可以实现分布式对象。主要应用例如:RMI要利用对象序列化运行远程主机上的服务,就像在本地机上运行对象时一样。
二:java对象序列化不仅保留一个对象的数据,而且递归保存对象引用的每个对象的数据。可以将整个对象层次写入字节流中,可以保存在文件中或在网络连接上传递。利用对象序列化可以进行对象的"深复制",即复制对象本身及引用的对象本身。序列化一个对象可能得到整个对象序列。

 

=======================

可以看看接口java.io.serializable的中文解释:

Serializable


public interface Serializable

类通过实现 java.io.Serializable 接口以启用其序列化功能。未实现此接口的类将无法使其任何状态序列化或反序列化。可序列化类的所有子类型本身都是可序列化的。序列化接口没有方法或字段,仅用于标识可序列化的语义。

要允许不可序列化类的子类型序列化,可以假定该子类型负责保存和还原超类型的公用 (public)、受保护的 (protected) 和(如果可访问)包 (package) 字段的状态。仅在子类型扩展的类有一个可访问的无参数构造方法来初始化该类的状态时,才可以假定子类型有此责任。如果不是这种情况,则声明一个类为可序列化类是错误的。该错误将在运行时检测到。

在反序列化过程中,将使用该类的公用或受保护的无参数构造方法初始化不可序列化类的字段。可序列化的子类必须能够访问无参数的构造方法。可序列化子类的字段将从该流中还原。

当遍历一个图形时,可能会遇到不支持可序列化接口的对象。在此情况下,将抛出 NotSerializableException,并将标识不可序列化对象的类。

在序列化和反序列化过程中需要特殊处理的类必须使用下列准确签名来实现特殊方法:

 

private void writeObject(java.io.ObjectOutputStream out)
throws IOException
private void readObject(java.io.ObjectInputStream in)
throws IOException, ClassNotFoundException;

writeObject 方法负责写入特定类的对象的状态,以便相应的 readObject 方法可以还原它。通过调用 out.defaultWriteObject 可以调用保存 Object 的字段的默认机制。该方法本身不需要涉及属于其超类或子类的状态。状态是通过使用 writeObject 方法或使用 DataOutput 支持的用于基本数据类型的方法将各个字段写入 ObjectOutputStream 来保存的。

readObject 方法负责从流中读取并还原类字段。它可以调用 in.defaultReadObject 来调用默认机制,以还原对象的非静态和非瞬态字段。defaultReadObject 方法使用流中的信息来分配流中通过当前对象中相应命名字段保存的对象的字段。这用于处理类发展后需要添加新字段的情形。该方法本身不需要涉及属于其超类或子类的状态。状态是通过使用 writeObject 方法或使用 DataOutput 支持的用于基本数据类型的方法将各个字段写入 ObjectOutputStream 来保存的。

将对象写入流时需要指定要使用的替代对象的可序列化类,应使用准确的签名来实现此特殊方法:

 

ANY-ACCESS-MODIFIER Object writeReplace() throws ObjectStreamException;

此 writeReplace 方法将由序列化调用,前提是如果此方法存在,而且它可以通过被序列化对象的类中定义的一个方法访问。因此,该方法可以拥有私有 (private)、受保护的 (protected) 和包私有 (package-private) 访问。子类对此方法的访问遵循 java 访问规则。

在从流中读取类的一个实例时需要指定替代的类应使用的准确签名来实现此特殊方法。

 

ANY-ACCESS-MODIFIER Object readResolve() throws ObjectStreamException;

此 readResolve 方法遵循与 writeReplace 相同的调用规则和访问规则。

序列化运行时使用一个称为 serialVersionUID 的版本号与每个可序列化类相关联,该序列号在反序列化过程中用于验证序列化对象的发送者和接收者是否为该对象加载了与序列化兼容的类。如果接收者加载的该对象的类的 serialVersionUID 与对应的发送者的类的版本号不同,则反序列化将会导致 InvalidClassException。可序列化类可以通过声明名为 "serialVersionUID" 的字段(该字段必须是静态 (static)、最终 (final) 的 long 型字段)显式声明其自己的 serialVersionUID:

 

ANY-ACCESS-MODIFIER static final long serialVersionUID = 42L;

如果可序列化类未显式声明 serialVersionUID,则序列化运行时将基于该类的各个方面计算该类的默认 serialVersionUID 值,如“Java(TM) 对象序列化规范”中所述。不过,强烈建议 所有可序列化类都显式声明 serialVersionUID 值,原因计算默认的 serialVersionUID 对类的详细信息具有较高的敏感性,根据编译器实现的不同可能千差万别,这样在反序列化过程中可能会导致意外的 InvalidClassException。因此,为保证 serialVersionUID 值跨不同 java 编译器实现的一致性,序列化类必须声明一个明确的 serialVersionUID 值。还强烈建议使用 private 修改器显示声明 serialVersionUID(如果可能),原因是这种声明仅应用于立即声明类 -- serialVersionUID 字段作为继承成员没有用处。

 

本文来自CSDN博客,转载请标明出处:http://blog.csdn.net/robinyyh/archive/2007/01/11/1480603.aspx

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值