java序列化

本文介绍Java序列化技术。
1.Java的"对象序列化"
Java 的"对象序列化"能让你将一个实现了Serializable接口的对象转换成一组byte,这样日后要用这个对象时候,你就能把这些byte数据恢复出 来,并据此重新构建那个对象。这一点甚至在跨网络的环境下也是如此,这就意味着序列化机制能自动补偿操作系统方面的差异。也就是说,你可以在 Windows机器上创键一个对象,序列化之后,再通过网络传到Unix机器上,然后在那里进行重建。你不用担心在不同的平台上数据是怎样表示的, byte顺序怎样,或者别的什么细节。
对象序列化最聪明的一点是,它不仅能保存对象的副本,而且还会跟踪对象里面的reference,把它所引 用的对象也保存起来,然后再继续跟踪那些对象的 reference,以此类推。这种情形常被称为"单个对象所联结的'对象网'"。这个机制所涵盖的范围不仅包括对象的成员数据,而且还包含数组里面的 reference。如果你要自己实现对象序列化的话,那么编写跟踪这些链接的程序将会是一件非常痛苦的任务。但是,Java的对象序列化就能精确无误地 做到这一点,毫无疑问,它的遍历算法是做过优化的。
2.Object serialization的定义
Object serialization 允许你将实现了 Serializable接口的对象转换为字节序列,这些字节序列可以被完全存储以备以后重新生成原来的对象。
serialization 不但可以在本机做,而且可以经由网络操作(RMI)。这个好处是很大的----因为它自动屏蔽了操作系统的差异,字节顺序(用Unix下的c开发过网络编 程的人应该知道这个概念)等。比如,在 Window平台生成一个对象并序列化之,然后通过网络传到一台Unix机器上, 然后可以在这台Unix机器上正确地重构这个对象。
Object serialization主要用来支持2种主要的特性:
Java的 RMI(remote method invocation).RMI允许象在本机上一样操作远程机器上的对象。当发送消息给远程对象时,就需要用到 serializaiton机制来发送参数和接收返回直。
Java的JavaBeans。Bean的状态信息通常是在设计时配置的。Bean的状态信息必须被存起来,以便当程序运行时能恢复这些状态信息。这也需要serializaiton机制。
3.一般序列化实例
程序名称:SerializationDemo.java
程序主题:实现对象的序列化和反序列化
程序说明:该程序由实例化一个MyClass类的对象开始,该对象有三个实例变量,类型分别为String、int、double,是希望存储和恢复的信息。
import java.io.Serializable;
class MyClass implements Serializable {
    String s;
    int i;
    double d;

    public MyClass(String s, int i, double d) {
        this.s = s;
       this.i = i;
       this.d = d;
    }

    public String toString() {
       return "s=" + s + ";i=" + i + ";d=" + d;
    }
}
要 想序列化对象,你必须先创建一个OutputStream,然后把它嵌进ObjectOutputStream。这时,你就能用writeObject ( )方法把对象写入OutputStream了。读的时候,你得把 InputStream嵌到ObjectInputStream里面,然后再调用 readObject( )方法。
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;

public class SerializationDemo {
        public static void main(String args[]) {
       FileInputStream in = null;
       FileOutputStream out = null;
       ObjectInputStream oin = null;
       ObjectOutputStream oout = null;
       MyClass object1 = new MyClass("Hello", -7, 2.7e10);
       System.out.println("object1:" + object1);
       // Object serialization
       try {
           out = new FileOutputStream("serial.txt");
           oout = new ObjectOutputStream(out);
           oout.writeObject(object1);
           oout.close();
       } catch (Exception e) {
           System.out.println("Exception during serialization:" + e);
           System.exit(0);
       }

       // Object deserialization
        try {
           MyClass object2;
           in = new FileInputStream("serial");
           oin = new ObjectInputStream(in);
           object2 = (MyClass) oin.readObject();
           oin.close();
           System.out.println("object2:" + object2);
       } catch (Exception e) {
           System.out.println("Exception during deserialization:" + e);
           System.exit(0);
       } finally {
           // … 此处省略
       }
    }
}
结果:
object1:s=Hello;i=-7;d=2.7E10
object2:s=Hello;i=-7;d=2.7E10
4.修改默认的序列化机制
在 序列化的过程中,有些数据字段我们不想将其序列化,对于此类字段我们只需要在定义时给它加上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类中的一个数据字段,并演示如何将其序列化。
import java.awt.geom.Point2D;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
class LabeledPoint implements Serializable {
    private String label;
    transient private Point2D.Double point;

    public LabeledPoint(String label, double x, double y) {
       super();
       this.label = label;
       this.point = new Point2D.Double(x, y);
    }

    private void writeObject(ObjectOutputStream oos) throws IOException {
       oos.defaultWriteObject();// 先序列化对象
       oos.writeDouble(point.getX());
       oos.writeDouble(point.getY());
    }

    private void readObject(ObjectInputStream ois) throws IOException,
           ClassNotFoundException {
       ois.defaultReadObject();// 先反序列化对象
       double x = ois.readDouble() + 1.0;
       double y = ois.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() + "]";
    }
}

public class Test1 {
    public static void main(String[] args) {
       LabeledPoint label = new LabeledPoint("Book", 5.0, 5.0);
       try {
           System.out.println("before:n" + label);
           ObjectOutputStream oos = new ObjectOutputStream(
                  new FileOutputStream("label.txt"));
           oos.writeObject(label);
           oos.close();

           ObjectInputStream ois = new ObjectInputStream(
new FileInputStream("label.txt"));
           LabeledPoint label1 = (LabeledPoint) ois.readObject();
           ois.close();
           System.out.println("after add 1.0:n" + label1);
       } catch (Exception e) {
           e.printStackTrace();
       }
    }
}
结果:
before:
sample.LabeledPoint[ Label = Book, point.getX() = 5.0, point.getY() = 5.0]
after add 1.0:
sample.LabeledPoint[ Label = Book, point.getX() = 6.0, point.getY() = 6.0]
5.继承类序列化实例
当一个父类实现Serializable接口后,他的子类都将自动的实现序列化。
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;

class SuperC implements Serializable {// 父类实现了序列化
    int supervalue;

    public SuperC(int supervalue) {
       this.supervalue = supervalue;
    }

    public String toString() {
       return "supervalue: " + supervalue;
    }
}

class SubC extends SuperC {// 子类
    int subvalue;

    public SubC(int supervalue, int subvalue) {
       super(supervalue);
       this.subvalue = subvalue;
    }

    public String toString() {
       return super.toString() + " sub: " + subvalue;
    }
}

public class Test2 {
    public static void main(String[] args) {
       SubC subc = new SubC(100, 200);
       FileInputStream in = null;
       FileOutputStream out = null;
       ObjectInputStream oin = null;
       ObjectOutputStream oout = null;
       try {
           out = new FileOutputStream("Test1.txt");
           oout = new ObjectOutputStream(out);
           oout.writeObject(subc); // 子类序列化
           oout.close();
           oout = null;

           in = new FileInputStream("Test1.txt");
           oin = new ObjectInputStream(in);
           SubC subc2 = (SubC) oin.readObject(); // 子类反序列化
           System.out.println(subc2);
       } catch (Exception ex) {
           ex.printStackTrace();
       } finally {
           // … 此处省略
       }
    }
}
结果:
supervalue: 100 sub: 200
可 见子类成功的序列化/反序列化了。怎管让子类实现序列化看起来是一件很简单的事情,但有的时候,往往我们不能够让父类实现Serializable接口, 原因是有时候父类是抽象的(这并没有关系),并且父类不能够强制每个子类都拥有序列化的能力。换句话说父类设计的目的仅仅是为了被继承。
要为一个没有实现Serializable接口的父类,编写一个能够序列化的子类要做两件事情:
其一、父类要有一个无参的constructor;
其二、子类要负责序列化(反序列化)父类的域。
  我们将SuperC的Serializable接口去掉,而给 SubC加上Serializable接口。运行后产生错误:
java.lang.Error: Unresolved compilation problem:
Serializable cannot be resolved or is not a valid superinterface
at Serial.SubC.<init>(SubC.java:15)
at Serial.Test1.main(Test1.java:19)
Exception in thread "main"
我们改写这个例子:
abstract class SuperC{
    int supervalue;

    public SuperC(int supervalue) {
       this.supervalue = supervalue;
    }

    public SuperC() {}//增加一个无参的constructor
   
    public String toString() {
       return "supervalue: " + supervalue;
    }
}

class SubC extends SuperC implements Serializable{// 子类
    int subvalue;

    public SubC(int supervalue, int subvalue) {
       super(supervalue);
       this.subvalue = subvalue;
    }

    public String toString() {
       return super.toString() + " sub: " + subvalue;
    }
   
    private void writeObject(java.io.ObjectOutputStream out) throws IOException {
       out.defaultWriteObject();// 先序列化对象
       out.writeInt(supervalue);// 再序列化父类的域
    }

    private void readObject(java.io.ObjectInputStream in) throws IOException,
           ClassNotFoundException {
       in.defaultReadObject();// 先反序列化对象
       supervalue = in.readInt();//再反序列化父类的域
    }
}
运 行结果证明了这种方法是正确的。在此处我们又用到了 writeObject/ readObject方法,以代替默认的行为。我们在序列化时,首先调用了 ObjectOutputStream的 defaultWriteObject,它使用默认的序列化行为,然后序列化父类的域;反序列化的时候也一样。
6.实现Externalizable接口
Externalizable 接口继承自Serializable接口,如果一个类实现了Externalizable接口,那么将完全由这个类控制自身的序列化行为。 Externalizable接口声明了两个方法:
public void readExternal(ObjectInput in) throws IOException , ClassNotFoundException;
public void writeExternal(ObjectOutput out) throws IOException;
前者负责序列化操作,后者负责反序列化操作。
在 对实现了Externalizable接口的类的对象进行反序列化时,会先调用类的不带参数的构造方法(回忆前两个例子,异曲同工),这是有别于默认反序 列方式的。如果把类的不带参数的构造方法删除,或者把该构造方法的访问权限设置为private、默认或protected级别,会抛出 java.io.InvalidException: no valid constructor异常。
class ExternalDemo implements Externalizable { // ExternalDemo 类必须实现Externalizable接口
    private String aString = "TEST";
    private int num = 0;

    public ExternalDemo() {}

    public void writeExternal(ObjectOutput out) throws IOException {
       out.writeObject(aString);
       out.write(88); // 在序列化的数据最后加个88
    }

    public void readExternal(ObjectInput in) throws IOException,
           ClassNotFoundException {
       aString = (String) in.readObject();
       num = in.read(); // 把数字88加进来
    }

    public String toString() { // 测试
       return ("String:"+aString + " int:"+num);
    }
}

public class Test3 {
    public static void main(String[] args) {
       ExternalDemo eDemo = new ExternalDemo();
       try {
           ObjectOutputStream oos = new ObjectOutputStream(
                  new FileOutputStream("test3.txt"));
           oos.writeObject(eDemo); // writeExternal() 自动执行
           oos.close();

           ObjectInputStream ois = new ObjectInputStream(
                  new FileInputStream("test3.txt"));
           ExternalDemo demo = (ExternalDemo) ois.readObject(); // readExternal()自动执行
           System.out.print(demo);
           ois.close();
       } catch (Exception e) {
           e.printStackTrace();
       }
    }
}
结果:
String:TEST int:88
7.可序列化类的不同版本的序列化兼容性
凡是实现Serializable接口的类都有一个表示序列化版本标识符的静态变量:
private static final long serialVersionUID ;
以上serialVersionUID的取值是Java运行时环境根据类的内部细节自动生成的。如果对类的源代码作了修改,再重新编译,新生成的类文件的serialVersionUID的取值有可能也会发生变化。
类 的serialVersionUID的默认值完全依赖于Java编译器的实现,对于同一个类,用不同的Java编译器编译,有可能会导致不同的 serialVersionUID,也有可能相同。为了提高 serialVersionUID的独立性和确定性,强烈建议在一个可序列化类中显示的定义serialVersionUID,为它赋予明确的值。显式地 定义serialVersionUID有两种用途:
在某些场合,希望类的不同版本对序列化兼容,因此需要确保类的不同版本具有相同的serialVersionUID;
在某些场合,不希望类的不同版本对序列化兼容,因此需要确保类的不同版本具有不同的serialVersionUID。
8.总结Java序列化技术
如果一个对象的成员变量是一个对象,那么这个对象的数据成员也会被保存;
当一个对象的实例变量引用其他对象,序列化该对象时也把引用对象进行序列化;
当一个父类实现序列化,子类自动实现序列化,不需要显式实现Serializable接口;
如果一个可序列化的对象包含对某个不可序列化的对象的引用,那么整个序列化操作将会失败,并且会抛出一个 NotSerializableException。我们可以将这个引用标记为transient,那么对象仍然可以序列化;
并非所有的对象都可以序列化,,至于为什么不可以,有很多原因。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值