当前位置:七道奇文章资讯编程技术Java编程
日期:2011-03-22 16:17:00  来源:本站整理

java序列化的掌握[Java编程]

赞助商链接



  本文“java序列化的掌握[Java编程]”是由七道奇为您精心收集,来源于网络转载,文章版权归文章作者所有,本站不对其观点以及内容做任何评价,请读者自行判断,以下是其具体内容:
正如大家看到的那样,默许的序列化机制并不难操作.但是,假如有特别要求又该怎么办呢?我们大概有特别的安全问题,不但愿对象的某一部份序列化;大概某一个子对象完好没必要序列化,因为对象恢复今后,那一部份需求重新成立.
此时,通过实现Externalizable接口,用它替换Serializable接口,便可掌握序列化的具体历程.这个Externalizable接口扩大了Serializable,并增添了两个办法:writeExternal()和readExternal().在序列化和重新装配的历程中,会自动调用这两个办法,以便我们履行一些特别操作.
下面这个例子展示了Externalizable接口办法的简单利用.注意Blip1和Blip2几近完好一致,除了极细小的差别(自己研究一下代码,看看能否能发现):
//: Blips.java
// Simple use of Externalizable & a pitfall
import java.io.*;
import java.util.*;

class Blip1 implements Externalizable {
  public Blip1() {
    System.out.println("Blip1 Constructor");
  }
  public void writeExternal(ObjectOutput out)
      throws IOException {
    System.out.println("Blip1.writeExternal");
  }
  public void readExternal(ObjectInput in)
     throws IOException, ClassNotFoundException {
    System.out.println("Blip1.readExternal");
  }
}

class Blip2 implements Externalizable {
  Blip2() {
    System.out.println("Blip2 Constructor");
  }
  public void writeExternal(ObjectOutput out)
      throws IOException {
    System.out.println("Blip2.writeExternal");
  }
  public void readExternal(ObjectInput in)
     throws IOException, ClassNotFoundException {
    System.out.println("Blip2.readExternal");
  }
}

public class Blips {
  public static void main(String[] args) {
    System.out.println("Constructing objects:");
    Blip1 b1 = new Blip1();
    Blip2 b2 = new Blip2();
    try {
      ObjectOutputStream o =
        new ObjectOutputStream(
          new FileOutputStream("Blips.out"));
      System.out.println("Saving objects:");
      o.writeObject(b1);
      o.writeObject(b2);
      o.close();
      // Now get them back:
      ObjectInputStream in =
        new ObjectInputStream(
          new FileInputStream("Blips.out"));
      System.out.println("Recovering b1:");
      b1 = (Blip1)in.readObject();
      // OOPS! Throws an exception:
//!   System.out.println("Recovering b2:");
//!   b2 = (Blip2)in.readObject();
    } catch(Exception e) {
      e.printStackTrace();
    }
  }
} ///:~

该程序输出以下:
Constructing objects:
Blip1 Constructor
Blip2 Constructor
Saving objects:
Blip1.writeExternal
Blip2.writeExternal
Recovering b1:
Blip1 Constructor
Blip1.readExternal

未恢复Blip2对象的缘由是那样做会招致一个违例.你找出了Blip1和Blip2之间的辨别吗?Blip1的构建器是“大众的”(public),Blip2的构建器则不然,这样便会在恢复时造成违例.试试将Blip2的构建器属性变成“public”,然后删除//!注释标志,看看能否能得到精确的后果.
恢复b1后,会调用Blip1默许构建器.这与恢复一个Serializable(可序列化)对象差别.在后者的情形下,对象完好以它保存下来的二进制位为底子恢复,不存在构建器调用.而对一个Externalizable对象,全部普通的默许构建行为城市发生(包含在字段定义时的初始化),并且会调用readExternal().必须注意这一事实——分外注意全部默许的构建行为城市举行——不然很难在自己的Externalizable对象中产生精确的行为.
下面这个例子揭露了保存和恢复一个Externalizable对象必须做的全部事情:
//: Blip3.java
// Reconstructing an externalizable object
import java.io.*;
import java.util.*;

class Blip3 implements Externalizable {
  int i;
  String s; // No initialization
  public Blip3() {
    System.out.println("Blip3 Constructor");
    // s, i not initialized
  }
  public Blip3(String x, int a) {
    System.out.println("Blip3(String x, int a)");
    s = x;
    i = a;
    // s & i initialized only in non-default
    // constructor.
  }
  public String toString() { return s + i; }
  public void writeExternal(ObjectOutput out)
      throws IOException {
    System.out.println("Blip3.writeExternal");
    // You must do this:
    out.writeObject(s); out.writeInt(i);
  }
  public void readExternal(ObjectInput in)
     throws IOException, ClassNotFoundException {
    System.out.println("Blip3.readExternal");
    // You must do this:
    s = (String)in.readObject(); 
    i =in.readInt();
  }
  public static void main(String[] args) {
    System.out.println("Constructing objects:");
    Blip3 b3 = new Blip3("A String ", 47);
    System.out.println(b3.toString());
    try {
      ObjectOutputStream o =
        new ObjectOutputStream(
          new FileOutputStream("Blip3.out"));
      System.out.println("Saving object:");
      o.writeObject(b3);
      o.close();
      // Now get it back:
      ObjectInputStream in =
        new ObjectInputStream(
          new FileInputStream("Blip3.out"));
      System.out.println("Recovering b3:");
      b3 = (Blip3)in.readObject();
      System.out.println(b3.toString());
    } catch(Exception e) {
      e.printStackTrace();
    }
  }
} ///:~
此中,字段s和i只在第二个构建器中初始化,不关默许构建器的事.这意味着假定不在readExternal中初始化s和i,它们就会成为null(因为在对象成立的第一步中已将对象的存储空间排除为1).若注释掉跟随于“You must do this”背面的两行代码,并运路程序,就会发现当对象恢复今后,s是null,而i是零.
若从一个Externalizable对象担当,普通需求调用writeExternal()和readExternal()的底子类版本,以便精确地保存和恢复底子类组件.
所认为了让一切正常运作起来,千万不可仅在writeExternal()办法履行期间写入对象的重要数据(没有默许的行为可用来为一个Externalizable对象写入全部成员对象)的,而是必须在readExternal()办法中也恢复那些数据.初次操作时大概会有些不习惯,因为Externalizable对象的默许构建行为使其看起来仿佛正在举行某种存储与恢复操作.但实情并非如此.

1. transient(暂时)关键字
掌握序列化历程时,大概有一个特定的子对象不肯让Java的序列化机制自动保存与恢复.普通地,若那个子对象包含了不想序列化的敏感信息(如密码),就会面对这种情形.即便那种信息在对象中具有“private”(私有)属性,但一旦经序列化处理,人们便可以通过读取一个文件,大概拦阻网络传输得到它.
为避免对象的敏感部份被序列化,一个办法是将自己的类实现为Externalizable,就象前面展示的那样.这样一来,没有任何东西可以自动序列化,只能在writeExternal()明确序列化那些需求的部份.
但是,若操作的是一个Serializable对象,全部序列化操作城市自动举行.为办理这个问题,可以用transient(暂时)一一字段地关闭序列化,它的意思是“不要麻烦你(指自动机制)保存或恢复它了——我会自己处理的”.
比方,假定一个Login对象包含了与一个特定的登录会话有关的信息.校验登录的合理性时,普通都想将数据保存下来,但不包含密码.为做到这一点,最简单的办法是实现Serializable,并将password字段设为transient.下面是具体的代码:
//: Logon.java
// Demonstrates the "transient" keyword
import java.io.*;
import java.util.*;

class Logon implements Serializable {
  private Date date = new Date();
  private String username;
  private transient String password;
  Logon(String name, String pwd) {
    username = name;
    password = pwd;
  }
  public String toString() {
    String pwd =
      (password == null) ? "(n/a)" : password;
    return "logon info: \n   " +
      "username: " + username +
      "\n   date: " + date.toString() +
      "\n   password: " + pwd;
  }
  public static void main(String[] args) {
    Logon a = new Logon("Hulk", "myLittlePony");
    System.out.println( "logon a = " + a);
    try {
      ObjectOutputStream o =
        new ObjectOutputStream(
          new FileOutputStream("Logon.out"));
      o.writeObject(a);
      o.close();
      // Delay:
      int seconds = 5;
      long t = System.currentTimeMillis()
             + seconds * 1000;
      while(System.currentTimeMillis() < t)
        ;
      // Now get them back:
      ObjectInputStream in =
        new ObjectInputStream(
          new FileInputStream("Logon.out"));
      System.out.println(
        "Recovering object at " + new Date());
      a = (Logon)in.readObject();
      System.out.println( "logon a = " + a);
    } catch(Exception e) {
      e.printStackTrace();
    }
  }
} ///:~
可以看到,此中的date和username字段保持原始状况(未设成transient),所以会自动序列化.但是,password被设为transient,所以不会自动保存到磁盘;别的,自动序列化机制也不会作恢复它的尝试.输出以下:
logon a = logon info:
   username: Hulk
   date: Sun Mar 23 18:25:53 PST 1997
   password: myLittlePony
Recovering object at Sun Mar 23 18:25:59 PST 1997
logon a = logon info:
   username: Hulk
   date: Sun Mar 23 18:25:53 PST 1997
   password: (n/a)
一旦对象恢复成本来的模样,password字段就会变成null.注意必须用toString()查抄password能否为null,因为若用过载的“+”运算符来装配一个String对象,并且那个运算符碰到一个null句柄,就会造成一个名为NullPointerException的违例(新版Java大概会供应避免这个问题的代码).
我们也发现date字段被保存到磁盘,并从磁盘恢复,没有重新生成.
由于Externalizable对象默许时不保存它的任何字段,所以transient关键字只能伴随Serializable利用.

2. Externalizable的替换办法
若不是分外在乎要实现Externalizable接口,还有另一种办法可供选用.我们可以实现Serializable接口,并增添(注意是“增添”,而非“覆盖”大概“实现”)名为writeObject()和readObject()的办法.一旦对象被序列化大概重新装配,就会辨别调用那两个办法.也就是说,只要供应了这两个办法,就会优先利用它们,而不考虑默许的序列化机制.
这些办法必须含有下列精确的签名:
private void 
  writeObject(ObjectOutputStream stream)
    throws IOException;

private void 
  readObject(ObjectInputStream stream)
    throws IOException, ClassNotFoundException
从计划的角度动身,情形变得有些扑朔迷离.首先,大家大概认为这些办法不属于底子类大概Serializable接口的一部份,它们应当在自己的接口中得到定义.但请注意它们被定义成“private”,这意味着它们只能由这个类的其他成员调用.但是,我们实际并不从这个类的其他成员中调用它们,而是由ObjectOutputStream和ObjectInputStream的writeObject()及readObject()办法来调用我们对象的writeObject()和readObject()办法(注意我在这里用了很大的克制力来避免利用相同的办法名——因为怕混合).大家大概奇特ObjectOutputStream和ObjectInputStream若何有权拜候我们的类的private办法——只能认为这是序列化机制玩的一个把戏.
在任何情形下,接口中的定义的任何东西城市自动具有public属性,所以假如writeObject()和readObject()必须为private,那么它们不能成为接口(interface)的一部份.但由于我们精确地加上了签名,所以终究的效果实际与实现一个接口是相同的.
看起来仿佛我们调用ObjectOutputStream.writeObject()的时刻,我们传送给它的Serializable对象仿佛会被查抄能否实现了自己的writeObject().若答案是必定的是,便会跳过通例的序列化历程,并调用writeObject().readObject()也会碰到一样的情形.
还存在另一个问题.在我们的writeObject()内部,可以调用defaultWriteObject(),从而决意采纳默许的writeObject()行动.近似地,在readObject()内部,可以调用defaultReadObject().下面这个简单的例子演示了若何对一个Serializable对象的存储与恢复举行掌握:
//: SerialCtl.java
// Controlling serialization by adding your own
// writeObject() and readObject() methods.
import java.io.*;

public class SerialCtl implements Serializable {
  String a;
  transient String b;
  public SerialCtl(String aa, String bb) {
    a = "Not Transient: " + aa;
    b = "Transient: " + bb;
  }
  public String toString() {
    return a + "\n" + b;
  }
  private void 
    writeObject(ObjectOutputStream stream)
      throws IOException {
    stream.defaultWriteObject();
    stream.writeObject(b);
  }
  private void 
    readObject(ObjectInputStream stream)
      throws IOException, ClassNotFoundException {
    stream.defaultReadObject();
    b = (String)stream.readObject();
  }
  public static void main(String[] args) {
    SerialCtl sc = 
      new SerialCtl("Test1", "Test2");
    System.out.println("Before:\n" + sc);
    ByteArrayOutputStream buf = 
      new ByteArrayOutputStream();
    try {
      ObjectOutputStream o =
        new ObjectOutputStream(buf);
      o.writeObject(sc);
      // Now get it back:
      ObjectInputStream in =
        new ObjectInputStream(
          new ByteArrayInputStream(
            buf.toByteArray()));
      SerialCtl sc2 = (SerialCtl)in.readObject();
      System.out.println("After:\n" + sc2);
    } catch(Exception e) {
      e.printStackTrace();
    }
  }
} ///:~
在这个例子中,一个String保持原始状况,其他设为transient(暂时),以便证明非暂时字段会被defaultWriteObject()办法自动保存,而transient字段必须在程序中明确保存和恢复.字段是在构建器内部初始化的,而不是在定义的时刻,这证明了它们不会在重新装配的时刻被某些自动化机制初始化.
若预备通过默许机制写入对象的非transient部份,那么必须调用defaultWriteObject(),令其作为writeObject()中的第一个操作;并调用defaultReadObject(),令其作为readObject()的第一个操作.这些都是不常见的调用办法.举个例子来说,当我们为一个ObjectOutputStream调用defaultWriteObject()的时刻,并且没有为其传送参数,就需求采纳这种操作,使其知道对象的句柄以及若何写入全部非transient的部份.这种做法非常不便.
transient对象的存储与恢复采取了我们更熟习的代码.目前考虑一下会发生一些什么事情.在main()中会成立一个SerialCtl对象,随后会序列化到一个ObjectOutputStream里(注意这种情形下利用的是一个缓冲区,而非文件——与ObjectOutputStream完好一致).正式的序列化操作是在下面这行代码里发生的:
o.writeObject(sc);
此中,writeObject()办法必须查对sc,判断它能否有自己的writeObject()办法(不是查抄它的接口——它根本就没有,也不是查抄类的范例,而是操纵反射办法实际搜索办法).若答案是必定的,就利用那个办法.近似的情形也会在readObject()上发生.大概这是办理问题唯一实际的办法,但确切显得有些古怪.

3. 版本问题
有时刻大概想改变一个可序列化的类的版本(比方原始类的对象大概保存在数据库中).固然这种做法得到了支持,但普通只应在非常特别的情形下才用它.此外,它要求操作者对背后的原理有一个对比深的熟习,而我们在这里还不想到达这种深度.JDK 1.1的HTML文档对这一主题举行了非常全面的阐述(可从Sun公司下载,但大概也成了Java开辟包联机文档的一部份).
  以上是“java序列化的掌握[Java编程]”的内容,如果你对以上该文章内容感兴趣,你可以看看七道奇为您推荐以下文章:
  • 利用Javascript实现网页水印(非图片水印)
  • Java开辟环境的搭建
  • Ubuntu java安装与配置
  • 办理Ubuntu 10.04 Firefox3.6 Java浏览器插件不工作的问
  • Ubuntu重装后Java环境的设置
  • Sun Java进入Ubuntu 10.10软件中央
  • Ubuntu 10.10配置Java开辟环境
  • 在Ubuntu 10.10中配置Java环境变量的办法
  • Ubuntu下Java环境的搭建
  • Ubuntu 10.04 下安装 Java, JRE
  • Ubuntu 10.04下的搭建SUN JAVA开辟环境
  • Ubuntu 12.04安装java7
  • 本文地址: 与您的QQ/BBS好友分享!
    • 好的评价 如果您觉得此文章好,就请您
        0%(0)
    • 差的评价 如果您觉得此文章差,就请您
        0%(0)

    文章评论评论内容只代表网友观点,与本站立场无关!

       评论摘要(共 0 条,得分 0 分,平均 0 分) 查看完整评论
    Copyright © 2020-2022 www.xiamiku.com. All Rights Reserved .