信息发布软件,b2b软件,广告发布软件

 找回密码
 立即注册
搜索
查看: 3358|回复: 3
打印 上一主题 下一主题

[『 Java 图文教程』] Java对象序列化使用基础和实例教程

  [复制链接]

780

主题

864

帖子

5536

积分

积分
5536
跳转到指定楼层
宣传软件楼主
发表于 2016-10-20 15:27:00 | 只看该作者 |只看大图 回帖奖励 |倒序浏览 |阅读模式

软件教程首图:

软件教程分类:Java 图文教程 

软件图文教程视频教程分类:软件图文教程 

软件教程难易程度:软件高级教程 

软件教程发布日期:2016-10-20

软件教程关键字:

① 本信息收集于网络,如有不对的地方欢迎联系我纠正!
② 本信息免费收录,不存在价格的问题!
③ 如果您的网站也想这样出现在这里,请您加好友情链接,我当天会审核通过!

④友情链接关键字:软件定制网站 网址:http://www.postbbs.com

软件教程详细描述

所谓对象序列化就是将对象的状态转换成字节流,以后可以通过这些值再生成相同状态的对象。这个过程也可以通过网络实现,可以先在Windows机器上创建一个对象,对其序列化,然后通过网络发给一台Unix机器,然后在那里准确无误地重新"装配"。像RMI、Socket、JMS、EJB它们中的一种,彼此为什么能够传递Java对象,当然都是对象序列化机制的功劳。  

    Java对象序列化机制一般来讲有两种用途:

    Java的JavaBeans: Bean的状态信息通常是在设计时配置的,Bean的状态信息必须被存起来,以便当程序运行时能恢复这些状态信息,这需要将对象的状态保存到文件中,而后能够通过读入对象状态来重新构造对象,恢复程序状态。

    RMI允许象在本机上一样操作远程机器上的对象;或使用套接字在网络上传送对象的程序来说,这些都是需要实现serializaiton机制的。

    我们通过让类实现Java.io.Serializable 接口可以将类序列化。这个接口是一个制造者(marker)接口。也就是说,对于要实现它的类来说,该接口不需要实现任何方法。它主要用来通知Java虚拟机(JVM),需要将一个对象序列化。

    对于这个,有几点我们需要明确:

    并非所有类都可以序列化,在cmd下,我们输入serialver Java.net.Socket,可以得到socket是否可序列化的信息,实际上socket是不可序列化的。

    Java有很多基础类已经实现了serializable接口,比如string,vector等。但是比如hashtable就没有实现serializable接口。

    将对象读出或者写入流的主要类有两个: ObjectOutputStream与ObjectInputStream .ObjectOutputStream 提供用来将对象写入输出流的writeObject方法, ObjectInputStream提供从输入流中读出对象的readObject方法。使用这些方法的对象必须已经被序列化的。也就是说,必须已经实现 Serializable接口。如果你想writeobject一个hashtable对象,那么,会得到一个异常。

    序列化的过程就是对象写入字节流和从字节流中读取对象。将对象状态转换成字节流之后,可以用Java.io包中的各种字节流类将其保存到文件中,管道到另一线程中或通过网络连接将对象数据发送到另一主机。对象序列化功能非常简单、强大,在RMI、Socket、JMS、EJB都有应用。对象序列化问题在网络编程中并不是最激动人心的课题,但却相当重要,具有许多实用意义。

    对象序列化可以实现分布式对象。主要应用例如:RMI要利用对象序列化运行远程主机上的服务,就像在本地机上运行对象时一样。

    Java对象序列化不仅保留一个对象的数据,而且递归保存对象引用的每个对象的数据。可以将整个对象层次写入字节流中,可以保存在文件中或在网络连接上传递。利用对象序列化可以进行对象的“深复制”,即复制对象本身及引用的对象本身。序列化一个对象可能得到整个对象序列。

    Java序列化比较简单,通常不需要编写保存和恢复对象状态的定制代码。实现Java.io.Serializable接口的类对象可以转换成字节流或从字节流恢复,不需要在类中增加任何代码。只有极少数情况下才需要定制代码保存或恢复对象状态。这里要注意:不是每个类都可序列化,有些类是不能序列化的,例如涉及线程的类与特定JVM有非常复杂的关系。


    序列化机制:

    序列化分为两大部分:序列化 和反序列化 。序列化是这个过程的第一部分,将数据分解成字节流,以便存储在文件中或在网络上传输。反序列化就是打开字节流并重构对象。对象序列化不仅要将基本数据类型转换成字节表示,有时还要恢复数据。恢复数据要求有恢复数据的对象实例。ObjectOutputStream中的序列化过程与字节流连接,包括对象类型和版本信息。反序列化时,JVM用头信息生成对象实例,然后将对象字节流中的数据复制到对象数据成员中。下面我们分两大部分来阐述:

    处理对象流:

    (序列化过程和反序列化过程)

    Java.io包有两个序列化对象的类。ObjectOutputStream负责将对象写入字节流,ObjectInputStream从字节流重构对象。

    我们先了解ObjectOutputStream类吧。ObjectOutputStream类扩展DataOutput接口。

    writeObject() 方法是最重要的方法,用于对象序列化。如果对象包含其他对象的引用,则writeObject()方法递归序列化这些对象。每个 ObjectOutputStream维护序列化的对象引用表,防止发送同一对象的多个拷贝。(这点很重要)由于writeObject()可以序列化整组交叉引用的对象,因此同一ObjectOutputStream实例可能不小心被请求序列化同一对象。这时,进行反引用序列化,而不是再次写入对象字节流。

    下面,让我们从例子中来了解ObjectOutputStream这个类吧。

// 序列化 today's date 到一个文件中.

FileOutputStream  f = new  FileOutputStream ("tmp" );

ObjectOutputStream  s = new  ObjectOutputStream (f);

s.writeObject("Today" );

s.writeObject(new  Date ());

s.flush();

    现在,让我们来了解ObjectInputStream这个类。它与ObjectOutputStream相似。它扩展DataInput接口。 ObjectInputStream中的方法镜像DataInputStream中读取Java基本数据类型的公开方法。readObject()方法从字节流中反序列化对象。每次调用readObject()方法都返回流中下一个Object。对象字节流并不传输类的字节码,而是包括类名及其签名。 readObject()收到对象时,JVM装入头中指定的类。如果找不到这个类,则readObject()抛出 ClassNotFoundException,如果需要传输对象数据和字节码,则可以用RMI框架。ObjectInputStream的其余方法用于定制反序列化过程。

    例子如下:

//从文件中反序列化 string 对象和 date 对象

FileInputStream  in = new  FileInputStream ("tmp" );

ObjectInputStream  s = new  ObjectInputStream (in);

String  today = (String )s.readObject();

Date  date = (Date )s.readObject();

    定制序列化过程:

    序列化通常可以自动完成,但有时可能要对这个过程进行控制。java可以将类声明为serializable,但仍可手工控制声明为static或transient的数据成员。

    例子:一个非常简单的序列化类。

public  class  simpleSerializableClass implements  Serializable {

String  sToday="Today:" ;

transient  Date  dtToday=new  Date ();

}

    序列化时,类的所有数据成员应可序列化除了声明为transient 或static的成员。将变量声明为transient告诉JVM我们会负责将变元序列化。将数据成员声明为transient后,序列化过程就无法将其加进对象字节流中,没有从transient数据成员发送的数据。后面数据反序列化时,要重建数据成员(因为它是类定义的一部分),但不包含任何数据,因为这个数据成员不向流中写入任何数据。记住,对象流不序列化static或transient。我们的类要用writeObject()与 readObject()方法以处理这些数据成员。使用writeObject()与readObject()方法时,还要注意按写入的顺序读取这些数据成员。

    关于如何使用定制序列化的部分代码如下

//重写writeObject()方法以便处理transient的成员。

public  void  writeObject(ObjectOutputStream  outputStream) throws  IOException {

outputStream.defaultWriteObject();//使定制的writeObject()方法可以

利用自动序列化中内置的逻辑。

outputStream.writeObject(oSocket.getInetAddress());

outputStream.writeInt(oSocket.getPort());

}

//重写readObject()方法以便接收transient的成员。

private  void  readObject(ObjectInputStream  inputStream) throws IOException ,

ClassNotFoundException {

inputStream.defaultReadObject();//defaultReadObject()补充自动序列化

InetAddress  oAddress=(InetAddress )inputStream.readObject();

int  iPort =inputStream.readInt();

oSocket = new  Socket (oAddress,iPort);

iID=getID();

dtToday =new  Date ();

}

    完全定制序列化过程:

    如果一个类要完全负责自己的序列化,则实现Externalizable接口而不是Serializable接口。Externalizable接口定义包括两个方法writeExternal()与readExternal()。利用这些方法可以控制对象数据成员如何写入字节流.类实现 Externalizable时,头写入对象流中,然后类完全负责序列化和恢复数据成员,除了头以外,根本没有自动序列化。这里要注意了。声明类实现 Externalizable接口会有重大的安全风险。writeExternal()与readExternal()方法声明为public,恶意类可以用这些方法读取和写入对象数据。如果对象包含敏感信息,则要格外小心。这包括使用安全套接或加密整个字节流。到此为至,我们学习了序列化的基础部分知识。



unto自媒体真的可以月入几万块钱吗 不管您信不信反正我觉得不是那么容易的事nextJava编程中的IO系统和多实例分解教程
回复

使用道具 举报

780

主题

864

帖子

5536

积分

积分
5536
信息发布软件沙发
 楼主| 发表于 2016-10-20 15:30:43 | 只看该作者
在这篇文章里,我们关注对象序列化。
  首先,我们来讨论一下什么是序列化以及序列化的原理;然后给出一个简单的示例来演示序列化和反序列化;有时有些信息是不应该被序列化的,我们应该如何控制;我们如何去自定义序列化内容;最后我们讨论一下在继承结构的场景中,序列化需要注意哪些内容。
  序列化概述
  序列化,简单来讲,就是以“流”的方式来保存对象,至于保存的目标地址,可以是文件,可以是数据库,也可以是网络,即通过网络将对象从一个节点传递到另一个节点。
  我们知道在Java的I/O结构中,有ObjectOutputStream和ObjectInputStream,它们可以实现将对象输出为二进制流,并从二进制流中获取对象,那为什么还需要序列化呢?这需要从Java变量的存储结构谈起,我们知道对Java来说,基础类型存储在栈上,复杂类型(引用类型)存储在堆中,对于基础类型来说,上述的操作时可行的,但对复杂类型来说,上述操作过程中,可能会产生重复的对象,造成错误。
  而序列化的工作流程如下:
  1)通过输出流保存的对象都有一个唯一的序列号。
  2)当一个对象需要保存时,先对其序列号进行检查。
  3)当保存的对象中已包含该序列号时,不需要再次保存,否则,进入正常保存的流程。
  正是通过序列号的机制,序列化才可以完整准确的保存对象的各个状态。
  序列化保存的是对象中的各个属性的值,而不是方法或者方法签名之类的信息。对于方法或者方法签名,只要JVM能够找到正确的ClassLoader,那么就可以invoke方法。
  序列化不会保存类的静态变量,因为静态变量是作用于类型,而序列化作用于对象。
  简单的序列化示例
  序列化的完整过程包括两部分:
  1)使用ObjectOutputStream将对象保存为二进制流,这一步叫做“序列化”。
  2)使用ObjectInputStream将二进制流转换成对象,这一步叫做“反序列化”。
  下面我们来演示一个简单的示例,首先定义一个Person对象,它包含name和age两个信息。
Java对象序列化使用基础和实例教程 b2b软件 定义Person对象
  然后是两个公共方法,用来完成读、写对象的操作:
Java对象序列化使用基础和实例教程 b2b软件
1 private static void writeObject(Object obj, String filePath) 2 { 3     try 4     { 5         FileOutputStream fos = new FileOutputStream(filePath); 6         ObjectOutputStream os = new ObjectOutputStream(fos); 7         os.writeObject(obj); 8         os.flush(); 9         fos.flush();10         os.close();11         fos.close();12         System.out.println("序列化成功。");13     }14     catch(Exception ex)15     {16         ex.printStackTrace();17     }18 }19 20 private static Object readObject(String filePath)21 {22     try23     {24         FileInputStream fis = new FileInputStream(filePath);25         ObjectInputStream is = new ObjectInputStream(fis);26         27         Object temp = is.readObject();28         29         fis.close();30         is.close();31         32         if (temp != null)33         {34             System.out.println("反序列化成功。");35             return temp;36         }37     }38     catch(Exception ex)39     {40         ex.printStackTrace();41     }42     43     return null;44 } Java对象序列化使用基础和实例教程 b2b软件

  这里,我们将对象保存的二进制流输出到磁盘文件中。
  接下来,我们首先来看“序列化”的方法:
Java对象序列化使用基础和实例教程 b2b软件
1 private static void serializeTest1()2 {3     Person person = new Person();4     person.setName("Zhang San");5     person.setAge(30);6     System.out.println(person);7     writeObject(person, "d:\\temp\\test\\person.obj");8 } Java对象序列化使用基础和实例教程 b2b软件

  我们定义了一个Person实例,然后将其保存到d:\temp\test\person.obj中。
  最后,是“反序列化”的方法:
Java对象序列化使用基础和实例教程 b2b软件
1 private static void deserializeTest1()2 {    3     Person temp = (Person)readObject("d:\\temp\\test\\person.obj");4     5     if (temp != null)6     {7         System.out.println(temp);8     }9 } Java对象序列化使用基础和实例教程 b2b软件

  它从d:\temp\test\person.obj中读取对象,然后进行输出。
  上述两个方法的执行结果如下:
Name:Zhang San; Age:30序列化成功。反序列化成功。Name:Zhang San; Age:30
  可以看出,读取的对象和保存的对象是完全一致的。
  隐藏非序列化信息
  有时,我们的业务对象中会包含很多属性,而有些属性是比较隐私的,例如年龄、银行卡号等,这些信息是不太适合进行序列化的,特别是在需要通过网络来传输对象信息时,这些敏感信息很容易被窃取。
  Java使用transient关键字来处理这种情况,针对那些敏感的属性,我们只需使用该关键字进行修饰,那么在序列化时,对应的属性值就不会被保存。
  我们还是看一个实例,这次我们定义一个新的Person2,其中age信息是我们不希望序列化的:
Java对象序列化使用基础和实例教程 b2b软件 定义Person2对象
  注意age的声明语句:
1 private transient int age;
  下面是“序列化”和“反序列化”的方法:
Java对象序列化使用基础和实例教程 b2b软件
1 private static void serializeTest2() 2 { 3     Person2 person = new Person2(); 4     person.setName("Zhang San"); 5     person.setAge(30); 6     System.out.println(person); 7     writeObject(person, "d:\\temp\\test\\person2.obj"); 8 } 9 10 private static void deserializeTest2()11 {    12     Person2 temp = (Person2)readObject("d:\\temp\\test\\person2.obj");13     14     if (temp != null)15     {16         System.out.println(temp);17     }18 } Java对象序列化使用基础和实例教程 b2b软件

  它的输出结果如下:
Name:Zhang San; Age:30序列化成功。反序列化成功。Name:Zhang San; Age:0
  可以看到经过反序列化的对象,age的信息变成了Integer的默认值0。
  自定义序列化过程
  我们可以对序列化的过程进行定制,进行更细粒度的控制。
  思路是在业务模型中添加readObject和writeObject方法。下面看一个实例,我们新建一个类型,叫Person3:
Java对象序列化使用基础和实例教程 b2b软件
1 class Person3 implements Serializable 2 { 3     private String name; 4     private transient int age; 5     public void setName(String name) { 6         this.name = name; 7     } 8     public String getName() { 9         return name;10     }11     public void setAge(int age) {12         this.age = age;13     }14     public int getAge() {15         return age;16     }17     18     public String toString()19     {20         return "Name:" + name + "; Age:" + age;21     }22     23     private void writeObject(ObjectOutputStream os)24     {25         try26         {27             os.defaultWriteObject();28             os.writeObject(this.age);29             System.out.println(this);30             System.out.println("序列化成功。");31         }32         catch(Exception ex)33         {34             ex.printStackTrace();35         }36     }37     38     private void readObject(ObjectInputStream is)39     {40         try41         {42             is.defaultReadObject();43             this.setAge(((Integer)is.readObject()).intValue() - 1);44             System.out.println("反序列化成功。");45             System.out.println(this);46         }47         catch(Exception ex)48         {49             ex.printStackTrace();50         }51     }52 } Java对象序列化使用基础和实例教程 b2b软件

  请注意观察readObject和writeObject方法,它们都是private的,接受的参数是ObjectStream,然后在方法体内调用了defaultReadObject或者defaultWriteObject方法。
  这里age同样是transient的,但是在保存对象的过程中,我们单独对其进行了保存,在读取时,我们将age信息读取出来,并进行了减1处理。
  下面是测试方法:
Java对象序列化使用基础和实例教程 b2b软件
1 private static void serializeTest3() 2 { 3     Person3 person = new Person3(); 4     person.setName("Zhang San"); 5     person.setAge(30); 6     System.out.println(person); 7     try 8     { 9         FileOutputStream fos = new FileOutputStream("d:\\temp\\test\\person3.obj");10         ObjectOutputStream os = new ObjectOutputStream(fos);11         os.writeObject(person);12         fos.close();13         os.close();14     }15     catch(Exception ex)16     {17         ex.printStackTrace();18     }19 }20 21 private static void deserializeTest3()22 {    23     try24     {25         FileInputStream fis = new FileInputStream("d:\\temp\\test\\person3.obj");26         ObjectInputStream is = new ObjectInputStream(fis);27         is.readObject();28         fis.close();29         is.close();30     }31     catch(Exception ex)32     {33         ex.printStackTrace();34     }35 } Java对象序列化使用基础和实例教程 b2b软件

  输出结果如下:
Name:Zhang San; Age:30序列化成功。反序列化成功。Name:Zhang San; Age:29
  可以看到,经过反序列化得到的对象,其age属性已经减1。
  探讨serialVersionUID
  在上文中,我们描述序列化原理时,曾经提及每个对象都会有一个唯一的序列号,这个序列号,就是serialVersionUID。
  当我们的对象实现Serializable接口时,该接口可以为我们生成serialVersionUID。
  有两种方式来生成serialVersionUID,一种是固定值:1L,一种是经过JVM计算,不同的JVM采取的计算算法可能不同。
  下面就是两个serialVersionUID的示例:
1 private static final long serialVersionUID = 1L;2 private static final long serialVersionUID = -2380764581294638541L;
  第一行是采用固定值生成的;第二行是JVM经过计算得出的。
  那么serialVersionUID还有其他用途吗?
  我们可以使用它来控制版本兼容。如果采用JVM生成的方式,我们可以看到,当我们业务对象的代码保持不变时,多次生成的serialVersionUID也是不变的,当我们对属性进行修改时,重新生成的serialVersionUID会发生变化,当我们对方法进行修改时,serialVersionUID不变。这也从另一个侧面说明,序列化是作用于对象属性上的。
  当我们先定义了业务对象,然后对其示例进行了“序列化”,这时根据业务需求,我们修改了业务对象,那么之前“序列化”后的内容还能经过“反序列化”返回到系统中吗?这取决于业务对象是否定义了serialVersionUID,如果定义了,那么是可以返回的,如果没有定义,会抛出异常。
  来看下面的示例,定义新的类型Person4:
Java对象序列化使用基础和实例教程 b2b软件
1 class Person4 implements Serializable 2 { 3     private String name; 4     private int age; 5     public void setName(String name) { 6         this.name = name; 7     } 8     public String getName() { 9         return name;10     }11     public void setAge(int age) {12         this.age = age;13     }14     public int getAge() {15         return age;16     }17     private void xxx(){}18     19     public String toString()20     {21         return "Name:" + name + "; Age:" + age;22     }23 } Java对象序列化使用基础和实例教程 b2b软件

  然后运行下面的方法:
Java对象序列化使用基础和实例教程 b2b软件
1 private static void serializeTest4()2 {3     Person4 person = new Person4();4     person.setName("Zhang San");5     person.setAge(30);6     7     writeObject(person, "d:\\temp\\test\\person4.obj");8 } Java对象序列化使用基础和实例教程 b2b软件

  接下来修改Person4,追加address属性:
Java对象序列化使用基础和实例教程 b2b软件
1 class Person4 implements Serializable 2 { 3     private String name; 4     private int age; 5     private String address; 6     public void setName(String name) { 7         this.name = name; 8     } 9     public String getName() {10         return name;11     }12     public void setAge(int age) {13         this.age = age;14     }15     public int getAge() {16         return age;17     }18     private void xxx(){}19     20     public String toString()21     {22         return "Name:" + name + "; Age:" + age;23     }24     public void setAddress(String address) {25         this.address = address;26     }27     public String getAddress() {28         return address;29     }30 } Java对象序列化使用基础和实例教程 b2b软件

  然后运行“反序列化”方法:
Java对象序列化使用基础和实例教程 b2b软件
1 private static void deserializeTest4()2 {    3     Person4 temp = (Person4)readObject("d:\\temp\\test\\person4.obj");4     5     if (temp != null)6     {7         System.out.println(temp);8     }9 } Java对象序列化使用基础和实例教程 b2b软件

  可以看到,运行结果如下:
Java对象序列化使用基础和实例教程 b2b软件
java.io.InvalidClassException: sample.serialization.Person4; local class incompatible: stream classdesc serialVersionUID = -2380764581294638541, local class serialVersionUID = -473458100724786987    at java.io.ObjectStreamClass.initNonProxy(Unknown Source)    at java.io.ObjectInputStream.readNonProxyDesc(Unknown Source)    at java.io.ObjectInputStream.readClassDesc(Unknown Source)    at java.io.ObjectInputStream.readOrdinaryObject(Unknown Source)    at java.io.ObjectInputStream.readObject0(Unknown Source)    at java.io.ObjectInputStream.readObject(Unknown Source)    at sample.serialization.Sample.readObject(Sample.java:158)    at sample.serialization.Sample.deserializeTest4(Sample.java:105)    at sample.serialization.Sample.main(Sample.java:16) Java对象序列化使用基础和实例教程 b2b软件

  但是当我们在Person4中添加serialVersionUID后,再次执行上述各步骤,得出的运行结果如下:
反序列化成功。Name:Zhang San; Age:30
  有继承结构的序列化
  业务对象会产生继承,这在管理系统中是经常看到的,如果我们有下面的业务对象:
Java对象序列化使用基础和实例教程 b2b软件
1 class Person5 2 { 3     private String name; 4     private int age; 5     public void setName(String name) { 6         this.name = name; 7     } 8     public String getName() { 9         return name;10     }11     public void setAge(int age) {12         this.age = age;13     }14     public int getAge() {15         return age;16     }17     18     public String toString()19     {20         return "Name:" + name + "; Age:" + age;21     }22     23     public Person5(String name, int age)24     {25         this.name = name;26         this.age = age;27     }28 }29 30 class Employee extends Person5 implements Serializable31 {32     public Employee(String name, int age) {33         super(name, age);34     }35 36     private String companyName;37 38     public void setCompanyName(String companyName) {39         this.companyName = companyName;40     }41 42     public String getCompanyName() {43         return companyName;44     }45     46     public String toString()47     {48         return "Name:" + super.getName() + "; Age:" + super.getAge() + "; Company:" + this.companyName;49     }50 } Java对象序列化使用基础和实例教程 b2b软件

  Employee继承在Person5,Employee实现了Serializable接口,Person5没有实现,那么运行下面的方法:
Java对象序列化使用基础和实例教程 b2b软件
1 private static void serializeTest5() 2 { 3     Employee emp = new Employee("Zhang San", 30); 4     emp.setCompanyName("XXX"); 5      6     writeObject(emp, "d:\\temp\\test\\employee.obj"); 7 } 8  9 private static void deserializeTest5()10 {    11     Employee temp = (Employee)readObject("d:\\temp\\test\\employee.obj");12     13     if (temp != null)14     {15         System.out.println(temp);16     }17 } Java对象序列化使用基础和实例教程 b2b软件

  会正常运行吗?事实上不会,它会抛出如下异常:
Java对象序列化使用基础和实例教程 b2b软件
java.io.InvalidClassException: sample.serialization.Employee; no valid constructor    at java.io.ObjectStreamClass$ExceptionInfo.newInvalidClassException(Unknown Source)    at java.io.ObjectStreamClass.checkDeserialize(Unknown Source)    at java.io.ObjectInputStream.readOrdinaryObject(Unknown Source)    at java.io.ObjectInputStream.readObject0(Unknown Source)    at java.io.ObjectInputStream.readObject(Unknown Source)    at sample.serialization.Sample.readObject(Sample.java:158)    at sample.serialization.Sample.deserializeTest5(Sample.java:123)    at sample.serialization.Sample.main(Sample.java:18)

  原因:在有继承层次的业务对象,进行序列化时,如果父类没有实现Serializable接口,那么父类必须提供默认构造函数。
  我们为Person5添加如下默认构造函数:
1 public Person5()2 {3     this.name = "Test";4     this.age = 1;5 }
  再次运行上述代码,结果如下:
Name:Zhang San; Age:30; Company:XXX序列化成功。反序列化成功。Name:Test; Age:1; Company:XXX
  可以看到,反序列化后的结果,父类中的属性,已经被父类构造函数中的赋值代替了!
  因此,我们推荐在有继承层次的业务对象进行序列化时,父类也应该实现Serializable接口。我们对Person5进行修改,使其实现Serializable接口,执行结果如下:
Name:Zhang San; Age:30; Company:XXX序列化成功。反序列化成功。Name:Zhang San; Age:30; Company:XXX
  这正是我们期望的结果。


回复 支持 反对

使用道具 举报

780

主题

864

帖子

5536

积分

积分
5536
推广工具板凳
 楼主| 发表于 2016-10-20 15:40:37 | 只看该作者
Java中的对象的内部状态只保存在内存中,其生命周期最长与JVM的生命周期一样,即JVM停止之后,所有对象都会被销毁。但有时候,Java对象是需要持久化的,因此Java提供了一种对象持久化方式——对象序列化机制(Object serialization),可以很容易的在JVM中的活动对象和字节数组(流)之间进行转换。
对象序列化保存的是对象的”状态”,即它的成员变量。由此可知,对象序列化不会关注类中的静态变量。
除了在持久化对象时会用到对象序列化之外,当使用RMI(远程方法调用),或在网络中传递对象时,都会用到对象序列化。
注意:序列化的是对象(对象的状态,成员变量等),而不是类
简单示例?
1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

86

87

88

89

90

91

92

93

94

95

96

97

98

99

100

101

[size=1em]<code [size=1em]class[size=1em]=[size=1em]"hljs java"[size=1em]>[size=1em]package [size=1em]java_interview;
[size=1em]import [size=1em]java.io.File;
[size=1em]import [size=1em]java.io.FileInputStream;
[size=1em]import [size=1em]java.io.FileOutputStream;
[size=1em]import [size=1em]java.io.ObjectInputStream;
[size=1em]import [size=1em]java.io.ObjectOutputStream;
[size=1em]import [size=1em]java.io.Serializable;
[size=1em]import [size=1em]java.lang.reflect.Field;
[size=1em]import [size=1em]java.util.ArrayList;
[size=1em]import [size=1em]java.util.Arrays;
[size=1em]import [size=1em]java.util.Collections;
[size=1em]import [size=1em]java.util.Comparator;
[size=1em]import [size=1em]java.util.HashMap;
[size=1em]import [size=1em]java.util.HashSet;
[size=1em]import [size=1em]java.util.Iterator;
[size=1em]import [size=1em]java.util.List;
[size=1em]import [size=1em]java.util.Map;
[size=1em]import [size=1em]java.util.NavigableMap;
[size=1em]import [size=1em]java.util.Random;
[size=1em]import [size=1em]java.util.Scanner;
[size=1em]import [size=1em]java.util.TreeMap;
[size=1em]import [size=1em]java.util.TreeSet;
[size=1em]public [size=1em]class [size=1em]Test {

[size=1em]    [size=1em]public [size=1em]static [size=1em]void [size=1em]main(String[] args) [size=1em]throws [size=1em]Exception {

[size=1em]    [size=1em]}
[size=1em]}
[size=1em] [size=1em]enum [size=1em]Gender {
[size=1em]    [size=1em]MALE, FEMALE
[size=1em]}
[size=1em]class [size=1em]Person [size=1em]implements [size=1em]Serializable {

[size=1em]    [size=1em]private [size=1em]String name = [size=1em]null[size=1em];

[size=1em]    [size=1em]private [size=1em]Integer age = [size=1em]null[size=1em];

[size=1em]    [size=1em]private [size=1em]Gender gender = [size=1em]null[size=1em];

[size=1em]    [size=1em]public [size=1em]Person() {
[size=1em]        [size=1em]System.out.println([size=1em]"none-arg constructor"[size=1em]);
[size=1em]    [size=1em]}

[size=1em]    [size=1em]public [size=1em]Person(String name, Integer age, Gender gender) {
[size=1em]        [size=1em]System.out.println([size=1em]"arg constructor"[size=1em]);
[size=1em]        [size=1em]this[size=1em].name = name;
[size=1em]        [size=1em]this[size=1em].age = age;
[size=1em]        [size=1em]this[size=1em].gender = gender;
[size=1em]    [size=1em]}

[size=1em]    [size=1em]public [size=1em]String getName() {
[size=1em]        [size=1em]return [size=1em]name;
[size=1em]    [size=1em]}

[size=1em]    [size=1em]public [size=1em]void [size=1em]setName(String name) {
[size=1em]        [size=1em]this[size=1em].name = name;
[size=1em]    [size=1em]}

[size=1em]    [size=1em]public [size=1em]Integer getAge() {
[size=1em]        [size=1em]return [size=1em]age;
[size=1em]    [size=1em]}

[size=1em]    [size=1em]public [size=1em]void [size=1em]setAge(Integer age) {
[size=1em]        [size=1em]this[size=1em].age = age;
[size=1em]    [size=1em]}

[size=1em]    [size=1em]public [size=1em]Gender getGender() {
[size=1em]        [size=1em]return [size=1em]gender;
[size=1em]    [size=1em]}

[size=1em]    [size=1em]public [size=1em]void [size=1em]setGender(Gender gender) {
[size=1em]        [size=1em]this[size=1em].gender = gender;
[size=1em]    [size=1em]}

[size=1em]    [size=1em]@Override
[size=1em]    [size=1em]public [size=1em]String toString() {
[size=1em]        [size=1em]return [size=1em]"[" [size=1em]+ name + [size=1em]", " [size=1em]+ age + [size=1em]", " [size=1em]+ gender + [size=1em]"]"[size=1em];
[size=1em]    [size=1em]}
[size=1em]}
[size=1em]public [size=1em]class [size=1em]SimpleSerial {

[size=1em]    [size=1em]public [size=1em]static [size=1em]void [size=1em]main(String[] args) [size=1em]throws [size=1em]Exception {
[size=1em]        [size=1em]File file = [size=1em]new [size=1em]File([size=1em]"person.out"[size=1em]);

[size=1em]        [size=1em]ObjectOutputStream oout = [size=1em]new [size=1em]ObjectOutputStream([size=1em]new [size=1em]FileOutputStream(file));
[size=1em]        [size=1em]Person person = [size=1em]new [size=1em]Person([size=1em]"John"[size=1em], [size=1em]101[size=1em], Gender.MALE);
[size=1em]        [size=1em]oout.writeObject(person);
[size=1em]        [size=1em]oout.close();

[size=1em]        [size=1em]ObjectInputStream oin = [size=1em]new [size=1em]ObjectInputStream([size=1em]new [size=1em]FileInputStream(file));
[size=1em]        [size=1em]Object newPerson = oin.readObject(); [size=1em]// 没有强制转换到Person类型
[size=1em]        [size=1em]oin.close();
[size=1em]        [size=1em]System.out.println(newPerson);
[size=1em]    [size=1em]}
[size=1em]}
[size=1em]/*输出:
[size=1em]arg constructor
[size=1em][John, 101, MALE]

[size=1em]*/
[size=1em]</code>



此时必须注意的是,当重新读取被保存的Person对象时,并没有调用Person的任何构造器,看起来就像是直接使用字节将Person对象还原出来的。
当Person对象被保存到person.out文件中之后,我们可以在其它地方去读取该文件以还原对象,但必须确保该读取程序的CLASSPATH中包含有Person.class(哪怕在读取Person对象时并没有显示地使用Person类,如上例所示),否则会抛出ClassNotFoundException。
Serializable的作用
什么一个类实现了Serializable接口,它就可以被序列化呢?在上节的示例中,使用ObjectOutputStream来持久化对象,在该类中有如下代码:
?
1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

[size=1em]<code [size=1em]class[size=1em]=[size=1em]"hljs java"[size=1em]>[size=1em]private [size=1em]void [size=1em]writeObject0(Object obj, [size=1em]boolean [size=1em]unshared) [size=1em]throws [size=1em]IOException {

[size=1em]    [size=1em]if [size=1em](obj [size=1em]instanceof [size=1em]String) {
[size=1em]        [size=1em]writeString((String) obj, unshared);
[size=1em]    [size=1em]} [size=1em]else [size=1em]if [size=1em](cl.isArray()) {
[size=1em]        [size=1em]writeArray(obj, desc, unshared);
[size=1em]    [size=1em]} [size=1em]else [size=1em]if [size=1em](obj [size=1em]instanceof [size=1em]Enum) {
[size=1em]        [size=1em]writeEnum((Enum) obj, desc, unshared);
[size=1em]    [size=1em]} [size=1em]else [size=1em]if [size=1em](obj [size=1em]instanceof [size=1em]Serializable) {
[size=1em]        [size=1em]writeOrdinaryObject(obj, desc, unshared);
[size=1em]    [size=1em]} [size=1em]else [size=1em]{
[size=1em]        [size=1em]if [size=1em](extendedDebugInfo) {
[size=1em]            [size=1em]throw [size=1em]new [size=1em]NotSerializableException(cl.getName() + [size=1em]"\n"
[size=1em]                    [size=1em]+ debugInfoStack.toString());
[size=1em]        [size=1em]} [size=1em]else [size=1em]{
[size=1em]            [size=1em]throw [size=1em]new [size=1em]NotSerializableException(cl.getName());
[size=1em]        [size=1em]}
[size=1em]    [size=1em]}

[size=1em]}</code>



从上述代码可知,如果被写对象的类型是String,或数组,或Enum,或Serializable,那么就可以对该对象进行序列化,否则将抛出NotSerializableException。
默认序列化机制
如果仅仅只是让某个类实现Serializable接口,而没有其它任何处理的话,则就是使用默认序列化机制。
默认的序列化机制,是对对象的所有成员变量(静态变量不会被序列化,因为它不属于某个对象,是所有对象共享的)进行序列化
使用默认机制,在序列化对象时,不仅会序列化当前对象本身,还会对该对象引用的其它对象也进行序列化,同样地,这些其它对象引用的另外对象也将被序列化,以此类推。所以,如果一个对象包含的成员变量是容器类对象,而这些容器所含有的元素也是容器类对象,那么这个序列化的过程就会较复杂,开销也较大。
影响序列化
在现实应用中,有些时候不能使用默认序列化机制。比如,希望在序列化过程中忽略掉敏感数据,或者简化序列化过程。下面将介绍若干影响序列化的方法。
transient关键字
有些情况下,是不希望对象的所有成员变量都进行序列化,比如User中的passwd字段,这个是敏感数据,不希望它被序列化,那么就可以使用transient关键字。
transient,顾名思义,非持久化的。使用transient关键字修饰成员变量,能够使它在序列化的过程中被忽略。
此处将Person类中的age字段声明为transient,如下所示,
?
1

2

3

4

5

[size=1em]<code [size=1em]class[size=1em]=[size=1em]"hljs java"[size=1em]>[size=1em]public [size=1em]class [size=1em]Person [size=1em]implements [size=1em]Serializable {

[size=1em]    [size=1em]transient [size=1em]private [size=1em]Integer age = [size=1em]null[size=1em];

[size=1em]}</code>



再执行SimpleSerial应用程序,会有如下输出:
?
1

2

[size=1em]<code [size=1em]class[size=1em]=[size=1em]"hljs delphi"[size=1em]>arg constructor
[size=1em][John, [size=1em]null[size=1em], MALE]</code>



* writeObject()方法与readObject()方法*
对于上述已被声明为transitive的字段age,除了将transitive关键字去掉之外,是否还有其它方法能使它再次可被序列化?方法之一就是在Person类中添加两个方法:writeObject()与readObject(),如下所示:
?
1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

[size=1em]<code [size=1em]class[size=1em]=[size=1em]"hljs java"[size=1em]>[size=1em]public [size=1em]class [size=1em]Person [size=1em]implements [size=1em]Serializable {

[size=1em]    [size=1em]transient [size=1em]private [size=1em]Integer age = [size=1em]null[size=1em];


[size=1em]    [size=1em]private [size=1em]void [size=1em]writeObject(ObjectOutputStream out) [size=1em]throws [size=1em]IOException {
[size=1em]        [size=1em]out.defaultWriteObject();
[size=1em]        [size=1em]out.writeInt(age);
[size=1em]    [size=1em]}

[size=1em]    [size=1em]private [size=1em]void [size=1em]readObject(ObjectInputStream in) [size=1em]throws [size=1em]IOException, ClassNotFoundException {
[size=1em]        [size=1em]in.defaultReadObject();
[size=1em]        [size=1em]age = in.readInt();
[size=1em]    [size=1em]}
[size=1em]}</code>



在writeObject()方法中会先调用ObjectOutputStream中的defaultWriteObject()方法,该方法会执行默认的序列化机制,如5.1节所述,此时会忽略掉age字段。然后再调用writeInt()方法显示地将age字段写入到ObjectOutputStream中。readObject()的作用则是针对对象的读取,其原理与writeObject()方法相同。
再次执行SimpleSerial应用程序,则又会有如下输出:
?
1

2

[size=1em]<code [size=1em]class[size=1em]=[size=1em]"hljs delphi"[size=1em]>arg constructor
[size=1em][John, [size=1em]31[size=1em], MALE]</code>



必须注意地是,writeObject()与readObject()都是private方法,那么它们是如何被调用的呢?毫无疑问,是使用反射。详情可见ObjectOutputStream中的writeSerialData方法,以及ObjectInputStream中的readSerialData方法。
通过这两个方法,可以对特殊要求的字段,增加额外的加密,解密的代码
Externalizable接口
以上例子都是基于实现Serializable接口来实现序列化的,Externalizable接口继承于Serializable,通过实现Externalizable接口也能实现序列化,不同的是,序列化操作的细节需要自己实现,而且,必须提供public的无参构造函数,否则会出现以下错误:

Java对象序列化使用基础和实例教程 b2b软件

无论是使用transient关键字,还是使用writeObject()和readObject()方法,其实都是基于Serializable接口的序列化。JDK中提供了另一个序列化接口–Externalizable,使用该接口之后,之前基于Serializable接口的序列化机制就将失效。此时将Person类修改成如下,
?
1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

[size=1em]<code [size=1em]class[size=1em]=[size=1em]"hljs java"[size=1em]>[size=1em]public [size=1em]class [size=1em]Person [size=1em]implements [size=1em]Externalizable {

[size=1em]    [size=1em]private [size=1em]String name = [size=1em]null[size=1em];

[size=1em]    [size=1em]transient [size=1em]private [size=1em]Integer age = [size=1em]null[size=1em];

[size=1em]    [size=1em]private [size=1em]Gender gender = [size=1em]null[size=1em];

[size=1em]    [size=1em]public [size=1em]Person() {
[size=1em]        [size=1em]System.out.println([size=1em]"none-arg constructor"[size=1em]);
[size=1em]    [size=1em]}

[size=1em]    [size=1em]public [size=1em]Person(String name, Integer age, Gender gender) {
[size=1em]        [size=1em]System.out.println([size=1em]"arg constructor"[size=1em]);
[size=1em]        [size=1em]this[size=1em].name = name;
[size=1em]        [size=1em]this[size=1em].age = age;
[size=1em]        [size=1em]this[size=1em].gender = gender;
[size=1em]    [size=1em]}

[size=1em]    [size=1em]private [size=1em]void [size=1em]writeObject(ObjectOutputStream out) [size=1em]throws [size=1em]IOException {
[size=1em]        [size=1em]out.defaultWriteObject();
[size=1em]        [size=1em]out.writeInt(age);
[size=1em]    [size=1em]}

[size=1em]    [size=1em]private [size=1em]void [size=1em]readObject(ObjectInputStream in) [size=1em]throws [size=1em]IOException, ClassNotFoundException {
[size=1em]        [size=1em]in.defaultReadObject();
[size=1em]        [size=1em]age = in.readInt();
[size=1em]    [size=1em]}

[size=1em]    [size=1em]@Override
[size=1em]    [size=1em]public [size=1em]void [size=1em]writeExternal(ObjectOutput out) [size=1em]throws [size=1em]IOException {

[size=1em]    [size=1em]}

[size=1em]    [size=1em]@Override
[size=1em]    [size=1em]public [size=1em]void [size=1em]readExternal(ObjectInput in) [size=1em]throws [size=1em]IOException, ClassNotFoundException {

[size=1em]    [size=1em]}

[size=1em]}</code>



此时再执行SimpleSerial程序之后会得到如下结果:
?
1

2

3

[size=1em]<code [size=1em]class[size=1em]=[size=1em]"hljs delphi"[size=1em]>arg constructor
[size=1em]none-arg constructor
[size=1em][[size=1em]null[size=1em], [size=1em]null[size=1em], [size=1em]null[size=1em]]</code>



从该结果,一方面可以看出Person对象中任何一个字段都没有被序列化。另一方面,如果细心的话,还可以发现这此次序列化过程调用了Person类的无参构造器。
另外,若使用Externalizable进行序列化,当读取对象时,会调用被序列化类的无参构造器去创建一个新的对象,然后再将被保存对象的字段的值分别填充到新对象中。这就是为什么在此次序列化过程中Person类的无参构造器会被调用。由于这个原因,实现Externalizable接口的类必须要提供一个无参的构造器,且它的访问权限为public。
对上述Person类作进一步的修改,使其能够对name与age字段进行序列化,但要忽略掉gender字段,如下代码所示:
?
1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

[size=1em]<code [size=1em]class[size=1em]=[size=1em]"hljs java"[size=1em]>[size=1em]public [size=1em]class [size=1em]Person [size=1em]implements [size=1em]Externalizable {

[size=1em]    [size=1em]private [size=1em]String name = [size=1em]null[size=1em];

[size=1em]    [size=1em]transient [size=1em]private [size=1em]Integer age = [size=1em]null[size=1em];

[size=1em]    [size=1em]private [size=1em]Gender gender = [size=1em]null[size=1em];

[size=1em]    [size=1em]public [size=1em]Person() {
[size=1em]        [size=1em]System.out.println([size=1em]"none-arg constructor"[size=1em]);
[size=1em]    [size=1em]}

[size=1em]    [size=1em]public [size=1em]Person(String name, Integer age, Gender gender) {
[size=1em]        [size=1em]System.out.println([size=1em]"arg constructor"[size=1em]);
[size=1em]        [size=1em]this[size=1em].name = name;
[size=1em]        [size=1em]this[size=1em].age = age;
[size=1em]        [size=1em]this[size=1em].gender = gender;
[size=1em]    [size=1em]}

[size=1em]    [size=1em]private [size=1em]void [size=1em]writeObject(ObjectOutputStream out) [size=1em]throws [size=1em]IOException {
[size=1em]        [size=1em]out.defaultWriteObject();
[size=1em]        [size=1em]out.writeInt(age);
[size=1em]    [size=1em]}

[size=1em]    [size=1em]private [size=1em]void [size=1em]readObject(ObjectInputStream in) [size=1em]throws [size=1em]IOException, ClassNotFoundException {
[size=1em]        [size=1em]in.defaultReadObject();
[size=1em]        [size=1em]age = in.readInt();
[size=1em]    [size=1em]}

[size=1em]    [size=1em]@Override
[size=1em]    [size=1em]public [size=1em]void [size=1em]writeExternal(ObjectOutput out) [size=1em]throws [size=1em]IOException {
[size=1em]        [size=1em]out.writeObject(name);
[size=1em]        [size=1em]out.writeInt(age);
[size=1em]    [size=1em]}

[size=1em]    [size=1em]@Override
[size=1em]    [size=1em]public [size=1em]void [size=1em]readExternal(ObjectInput in) [size=1em]throws [size=1em]IOException, ClassNotFoundException {
[size=1em]        [size=1em]name = (String) in.readObject();
[size=1em]        [size=1em]age = in.readInt();
[size=1em]    [size=1em]}

[size=1em]}</code>



执行SimpleSerial之后会有如下结果:
?
1

2

3

[size=1em]<code [size=1em]class[size=1em]=[size=1em]"hljs delphi"[size=1em]>arg constructor
[size=1em]none-arg constructor
[size=1em][John, [size=1em]31[size=1em], [size=1em]null[size=1em]]</code>



可知:采用这种方法实现序列化,transient是不起作用的,如果你不想序列化某个成员变量,只要在readExternal和writeExternal中不对该变量进行相应操作就可以了。
readResolve()方法
当我们使用Singleton模式时,应该是期望某个类的实例应该是唯一的,但如果该类是可序列化的,那么情况可能会略有不同。此时对第2节使用的Person类进行修改,使其实现Singleton模式,如下所示:
?
1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

[size=1em]<code [size=1em]class[size=1em]=[size=1em]"hljs java"[size=1em]>[size=1em]public [size=1em]class [size=1em]Person [size=1em]implements [size=1em]Serializable {

[size=1em]    [size=1em]private [size=1em]static [size=1em]class [size=1em]InstanceHolder {
[size=1em]        [size=1em]private [size=1em]static [size=1em]final [size=1em]Person instatnce = [size=1em]new [size=1em]Person([size=1em]"John"[size=1em], [size=1em]31[size=1em], Gender.MALE);
[size=1em]    [size=1em]}

[size=1em]    [size=1em]public [size=1em]static [size=1em]Person getInstance() {
[size=1em]        [size=1em]return [size=1em]InstanceHolder.instatnce;
[size=1em]    [size=1em]}

[size=1em]    [size=1em]private [size=1em]String name = [size=1em]null[size=1em];

[size=1em]    [size=1em]private [size=1em]Integer age = [size=1em]null[size=1em];

[size=1em]    [size=1em]private [size=1em]Gender gender = [size=1em]null[size=1em];

[size=1em]    [size=1em]private [size=1em]Person() {
[size=1em]        [size=1em]System.out.println([size=1em]"none-arg constructor"[size=1em]);
[size=1em]    [size=1em]}

[size=1em]    [size=1em]private [size=1em]Person(String name, Integer age, Gender gender) {
[size=1em]        [size=1em]System.out.println([size=1em]"arg constructor"[size=1em]);
[size=1em]        [size=1em]this[size=1em].name = name;
[size=1em]        [size=1em]this[size=1em].age = age;
[size=1em]        [size=1em]this[size=1em].gender = gender;
[size=1em]    [size=1em]}

[size=1em]}</code>



同时要修改SimpleSerial应用,使得能够保存/获取上述单例对象,并进行对象相等性比较,如下代码所示:
?
1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

[size=1em]<code [size=1em]class[size=1em]=[size=1em]"hljs cs"[size=1em]>[size=1em]public [size=1em]class [size=1em]SimpleSerial {

[size=1em]    [size=1em]public [size=1em]static [size=1em]void [size=1em]main(String[] args) [size=1em]throws [size=1em]Exception {
[size=1em]        [size=1em]File file = [size=1em]new [size=1em]File([size=1em]"person.out"[size=1em]);
[size=1em]        [size=1em]ObjectOutputStream oout = [size=1em]new [size=1em]ObjectOutputStream([size=1em]new [size=1em]FileOutputStream(file));
[size=1em]        [size=1em]oout.writeObject(Person.getInstance()); [size=1em]// 保存单例对象
[size=1em]        [size=1em]oout.close();

[size=1em]        [size=1em]ObjectInputStream oin = [size=1em]new [size=1em]ObjectInputStream([size=1em]new [size=1em]FileInputStream(file));
[size=1em]        [size=1em]Object newPerson = oin.readObject();
[size=1em]        [size=1em]oin.close();
[size=1em]        [size=1em]System.out.println(newPerson);

[size=1em]        [size=1em]System.out.println(Person.getInstance() == newPerson); [size=1em]// 将获取的对象与Person类中的单例对象进行相等性比较
[size=1em]    [size=1em]}
[size=1em]}</code>



执行上述应用程序后会得到如下结果:
?
1

2

3

[size=1em]<code [size=1em]class[size=1em]=[size=1em]"hljs delphi"[size=1em]>arg constructor
[size=1em][John, [size=1em]31[size=1em], MALE]
[size=1em]false[size=1em]</code>



值得注意的是,从文件person.out中获取的Person对象与Person类中的单例对象并不相等。为了能在序列化过程仍能保持单例的特性,可以在Person类中添加一个readResolve()方法,在该方法中直接返回Person的单例对象,如下所示:
?
1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

[size=1em]<code [size=1em]class[size=1em]=[size=1em]"hljs java"[size=1em]>[size=1em]public [size=1em]class [size=1em]Person [size=1em]implements [size=1em]Serializable {

[size=1em]    [size=1em]private [size=1em]static [size=1em]class [size=1em]InstanceHolder {
[size=1em]        [size=1em]private [size=1em]static [size=1em]final [size=1em]Person instatnce = [size=1em]new [size=1em]Person([size=1em]"John"[size=1em], [size=1em]31[size=1em], Gender.MALE);
[size=1em]    [size=1em]}

[size=1em]    [size=1em]public [size=1em]static [size=1em]Person getInstance() {
[size=1em]        [size=1em]return [size=1em]InstanceHolder.instatnce;
[size=1em]    [size=1em]}

[size=1em]    [size=1em]private [size=1em]String name = [size=1em]null[size=1em];

[size=1em]    [size=1em]private [size=1em]Integer age = [size=1em]null[size=1em];

[size=1em]    [size=1em]private [size=1em]Gender gender = [size=1em]null[size=1em];

[size=1em]    [size=1em]private [size=1em]Person() {
[size=1em]        [size=1em]System.out.println([size=1em]"none-arg constructor"[size=1em]);
[size=1em]    [size=1em]}

[size=1em]    [size=1em]private [size=1em]Person(String name, Integer age, Gender gender) {
[size=1em]        [size=1em]System.out.println([size=1em]"arg constructor"[size=1em]);
[size=1em]        [size=1em]this[size=1em].name = name;
[size=1em]        [size=1em]this[size=1em].age = age;
[size=1em]        [size=1em]this[size=1em].gender = gender;
[size=1em]    [size=1em]}

[size=1em]    [size=1em]private [size=1em]Object readResolve() [size=1em]throws [size=1em]ObjectStreamException {
[size=1em]        [size=1em]return [size=1em]InstanceHolder.instatnce;
[size=1em]    [size=1em]}

[size=1em]}</code>



再次执行本节的SimpleSerial应用后将有如下输出:
?
1

2

3

[size=1em]<code [size=1em]class[size=1em]=[size=1em]"hljs delphi"[size=1em]>arg constructor
[size=1em][John, [size=1em]31[size=1em], MALE]
[size=1em]true[size=1em]</code>



无论是实现Serializable接口,或是Externalizable接口,当从I/O流中读取对象时,readResolve()方法都会被调用到。实际上就是用readResolve()中返回的对象直接替换在反序列化过程中创建的对象,而被创建的对象则会被垃圾回收掉。
总结实现Serializable与Externalizable接口都可以实现序列化 前者实现方式如果不想序列化某个成员变量,使用transient关键字修饰该成员变量即可;如果想在此基础上添加一些自定义操作,在该类中实现writeObject与readObject方法(注意是private方法),在这两个方法里就可以做一些自定义操作,如改变某个成员变量的值。 后者实现方式,需要自己实现序列的细节(writeExternal与readExternal方法),并且必须提供一个public的无参构造函数。这种方式为自定义序列化提供了更多的灵活性。高级认识序列化 ID 问题
情境:
两个客户端 A 和 B 试图通过网络传递对象数据,A 端将对象 C 序列化为二进制数据再传给 B,B 反序列化得到 C。
问题:
C 对象的全类路径假设为 com.inout.Test,在 A 和 B 端都有这么一个类文件,功能代码完全一致。也都实现了 Serializable 接口,但是反序列化时总是提示不成功。
解决:
虚拟机是否允许反序列化,不仅取决于类路径和功能代码是否一致,一个非常重要的一点是两个类的序列化 ID 是否一致(就是 private static final long serialVersionUID = 1L)。清单 1 中,虽然两个类的功能代码完全一致,但是序列化 ID 不同,他们无法相互序列化和反序列化。
清单 1. 相同功能代码不同序列化 ID 的类对比
?
1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

[size=1em]<code [size=1em]class[size=1em]=[size=1em]"hljs java"[size=1em]> [size=1em]package [size=1em]com.inout;

[size=1em] [size=1em]import [size=1em]java.io.Serializable;

[size=1em] [size=1em]public [size=1em]class [size=1em]A [size=1em]implements [size=1em]Serializable {

[size=1em]     [size=1em]private [size=1em]static [size=1em]final [size=1em]long [size=1em]serialVersionUID = 1L;

[size=1em]     [size=1em]private [size=1em]String name;

[size=1em]     [size=1em]public [size=1em]String getName()
[size=1em]     [size=1em]{
[size=1em]         [size=1em]return [size=1em]name;
[size=1em]     [size=1em]}

[size=1em]     [size=1em]public [size=1em]void [size=1em]setName(String name)
[size=1em]     [size=1em]{
[size=1em]         [size=1em]this[size=1em].name = name;
[size=1em]     [size=1em]}
[size=1em] [size=1em]}

[size=1em] [size=1em]package [size=1em]com.inout;

[size=1em] [size=1em]import [size=1em]java.io.Serializable;

[size=1em] [size=1em]public [size=1em]class [size=1em]A [size=1em]implements [size=1em]Serializable {

[size=1em]     [size=1em]private [size=1em]static [size=1em]final [size=1em]long [size=1em]serialVersionUID = 2L;

[size=1em]     [size=1em]private [size=1em]String name;

[size=1em]     [size=1em]public [size=1em]String getName()
[size=1em]     [size=1em]{
[size=1em]         [size=1em]return [size=1em]name;
[size=1em]     [size=1em]}

[size=1em]     [size=1em]public [size=1em]void [size=1em]setName(String name)
[size=1em]     [size=1em]{
[size=1em]         [size=1em]this[size=1em].name = name;
[size=1em]</code>



如此问题中情景,客户端A将对象C序列化后,传给客户端B;客户端B对C进行反序列化时,B中必须有C对象对应的类,而且A、B客户端中对于对象C对应的类的序列化 ID 必须一致。
序列化 ID 在 Eclipse 下提供了两种生成策略,一个是固定的 1L,一个是随机生成一个不重复的 long 类型数据(实际上是使用 JDK 工具生成),在这里有一个建议,如果没有特殊需求,就是用默认的 1L 就可以,这样可以确保代码一致时反序列化成功。那么随机生成的序列化 ID 有什么作用呢,有些时候,通过改变序列化 ID 可以用来限制某些用户的使用。
序列化存储规则?
1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

[size=1em]<code [size=1em]class[size=1em]=[size=1em]"hljs avrasm"[size=1em]>ObjectOutputStream out = [size=1em]new [size=1em]ObjectOutputStream(
[size=1em]                    [size=1em]new [size=1em]FileOutputStream([size=1em]"result.obj"[size=1em]));
[size=1em]    [size=1em]Test test = [size=1em]new [size=1em]Test();
[size=1em]    [size=1em]//试图将对象两次写入文件
[size=1em]    [size=1em]out.writeObject(test);
[size=1em]    [size=1em]out.flush();
[size=1em]    [size=1em]System.out.println([size=1em]new [size=1em]File([size=1em]"result.obj"[size=1em]).length());
[size=1em]    [size=1em]out.writeObject(test);
[size=1em]    [size=1em]out.close();
[size=1em]    [size=1em]System.out.println([size=1em]new [size=1em]File([size=1em]"result.obj"[size=1em]).length());

[size=1em]    [size=1em]ObjectInputStream oin = [size=1em]new [size=1em]ObjectInputStream([size=1em]new [size=1em]FileInputStream(
[size=1em]            [size=1em]"result.obj"[size=1em]));
[size=1em]    [size=1em]//从文件依次读出两个文件
[size=1em]    [size=1em]Test t1 = (Test) oin.readObject();
[size=1em]    [size=1em]Test t2 = (Test) oin.readObject();
[size=1em]    [size=1em]oin.close();

[size=1em]    [size=1em]//判断两个引用是否指向同一个对象
[size=1em]    [size=1em]System.out.println(t1 == t2);
[size=1em]/*
[size=1em]31
[size=1em]36
[size=1em]true
[size=1em]*/

[size=1em]</code>



解答:Java 序列化机制为了节省磁盘空间,具有特定的存储规则,当写入文件的为同一对象时,并不会再将对象的内容进行存储,而只是再次存储一份引用,上面增加的 5 字节的存储空间就是新增引用和一些控制信息的空间。反序列化时,恢复引用关系,使得清单 3 中的 t1 和 t2 指向唯一的对象,二者相等,输出 true。该存储规则极大的节省了存储空间。


回复 支持 反对

使用道具 举报

780

主题

864

帖子

5536

积分

积分
5536
软件定制开发地板
 楼主| 发表于 2016-10-20 15:40:39 | 只看该作者
Java中的对象的内部状态只保存在内存中,其生命周期最长与JVM的生命周期一样,即JVM停止之后,所有对象都会被销毁。但有时候,Java对象是需要持久化的,因此Java提供了一种对象持久化方式——对象序列化机制(Object serialization),可以很容易的在JVM中的活动对象和字节数组(流)之间进行转换。
对象序列化保存的是对象的”状态”,即它的成员变量。由此可知,对象序列化不会关注类中的静态变量。
除了在持久化对象时会用到对象序列化之外,当使用RMI(远程方法调用),或在网络中传递对象时,都会用到对象序列化。
注意:序列化的是对象(对象的状态,成员变量等),而不是类
简单示例?
1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

86

87

88

89

90

91

92

93

94

95

96

97

98

99

100

101

[size=1em]<code [size=1em]class[size=1em]=[size=1em]"hljs java"[size=1em]>[size=1em]package [size=1em]java_interview;
[size=1em]import [size=1em]java.io.File;
[size=1em]import [size=1em]java.io.FileInputStream;
[size=1em]import [size=1em]java.io.FileOutputStream;
[size=1em]import [size=1em]java.io.ObjectInputStream;
[size=1em]import [size=1em]java.io.ObjectOutputStream;
[size=1em]import [size=1em]java.io.Serializable;
[size=1em]import [size=1em]java.lang.reflect.Field;
[size=1em]import [size=1em]java.util.ArrayList;
[size=1em]import [size=1em]java.util.Arrays;
[size=1em]import [size=1em]java.util.Collections;
[size=1em]import [size=1em]java.util.Comparator;
[size=1em]import [size=1em]java.util.HashMap;
[size=1em]import [size=1em]java.util.HashSet;
[size=1em]import [size=1em]java.util.Iterator;
[size=1em]import [size=1em]java.util.List;
[size=1em]import [size=1em]java.util.Map;
[size=1em]import [size=1em]java.util.NavigableMap;
[size=1em]import [size=1em]java.util.Random;
[size=1em]import [size=1em]java.util.Scanner;
[size=1em]import [size=1em]java.util.TreeMap;
[size=1em]import [size=1em]java.util.TreeSet;
[size=1em]public [size=1em]class [size=1em]Test {

[size=1em]    [size=1em]public [size=1em]static [size=1em]void [size=1em]main(String[] args) [size=1em]throws [size=1em]Exception {

[size=1em]    [size=1em]}
[size=1em]}
[size=1em] [size=1em]enum [size=1em]Gender {
[size=1em]    [size=1em]MALE, FEMALE
[size=1em]}
[size=1em]class [size=1em]Person [size=1em]implements [size=1em]Serializable {

[size=1em]    [size=1em]private [size=1em]String name = [size=1em]null[size=1em];

[size=1em]    [size=1em]private [size=1em]Integer age = [size=1em]null[size=1em];

[size=1em]    [size=1em]private [size=1em]Gender gender = [size=1em]null[size=1em];

[size=1em]    [size=1em]public [size=1em]Person() {
[size=1em]        [size=1em]System.out.println([size=1em]"none-arg constructor"[size=1em]);
[size=1em]    [size=1em]}

[size=1em]    [size=1em]public [size=1em]Person(String name, Integer age, Gender gender) {
[size=1em]        [size=1em]System.out.println([size=1em]"arg constructor"[size=1em]);
[size=1em]        [size=1em]this[size=1em].name = name;
[size=1em]        [size=1em]this[size=1em].age = age;
[size=1em]        [size=1em]this[size=1em].gender = gender;
[size=1em]    [size=1em]}

[size=1em]    [size=1em]public [size=1em]String getName() {
[size=1em]        [size=1em]return [size=1em]name;
[size=1em]    [size=1em]}

[size=1em]    [size=1em]public [size=1em]void [size=1em]setName(String name) {
[size=1em]        [size=1em]this[size=1em].name = name;
[size=1em]    [size=1em]}

[size=1em]    [size=1em]public [size=1em]Integer getAge() {
[size=1em]        [size=1em]return [size=1em]age;
[size=1em]    [size=1em]}

[size=1em]    [size=1em]public [size=1em]void [size=1em]setAge(Integer age) {
[size=1em]        [size=1em]this[size=1em].age = age;
[size=1em]    [size=1em]}

[size=1em]    [size=1em]public [size=1em]Gender getGender() {
[size=1em]        [size=1em]return [size=1em]gender;
[size=1em]    [size=1em]}

[size=1em]    [size=1em]public [size=1em]void [size=1em]setGender(Gender gender) {
[size=1em]        [size=1em]this[size=1em].gender = gender;
[size=1em]    [size=1em]}

[size=1em]    [size=1em]@Override
[size=1em]    [size=1em]public [size=1em]String toString() {
[size=1em]        [size=1em]return [size=1em]"[" [size=1em]+ name + [size=1em]", " [size=1em]+ age + [size=1em]", " [size=1em]+ gender + [size=1em]"]"[size=1em];
[size=1em]    [size=1em]}
[size=1em]}
[size=1em]public [size=1em]class [size=1em]SimpleSerial {

[size=1em]    [size=1em]public [size=1em]static [size=1em]void [size=1em]main(String[] args) [size=1em]throws [size=1em]Exception {
[size=1em]        [size=1em]File file = [size=1em]new [size=1em]File([size=1em]"person.out"[size=1em]);

[size=1em]        [size=1em]ObjectOutputStream oout = [size=1em]new [size=1em]ObjectOutputStream([size=1em]new [size=1em]FileOutputStream(file));
[size=1em]        [size=1em]Person person = [size=1em]new [size=1em]Person([size=1em]"John"[size=1em], [size=1em]101[size=1em], Gender.MALE);
[size=1em]        [size=1em]oout.writeObject(person);
[size=1em]        [size=1em]oout.close();

[size=1em]        [size=1em]ObjectInputStream oin = [size=1em]new [size=1em]ObjectInputStream([size=1em]new [size=1em]FileInputStream(file));
[size=1em]        [size=1em]Object newPerson = oin.readObject(); [size=1em]// 没有强制转换到Person类型
[size=1em]        [size=1em]oin.close();
[size=1em]        [size=1em]System.out.println(newPerson);
[size=1em]    [size=1em]}
[size=1em]}
[size=1em]/*输出:
[size=1em]arg constructor
[size=1em][John, 101, MALE]

[size=1em]*/
[size=1em]</code>



此时必须注意的是,当重新读取被保存的Person对象时,并没有调用Person的任何构造器,看起来就像是直接使用字节将Person对象还原出来的。
当Person对象被保存到person.out文件中之后,我们可以在其它地方去读取该文件以还原对象,但必须确保该读取程序的CLASSPATH中包含有Person.class(哪怕在读取Person对象时并没有显示地使用Person类,如上例所示),否则会抛出ClassNotFoundException。
Serializable的作用
什么一个类实现了Serializable接口,它就可以被序列化呢?在上节的示例中,使用ObjectOutputStream来持久化对象,在该类中有如下代码:
?
1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

[size=1em]<code [size=1em]class[size=1em]=[size=1em]"hljs java"[size=1em]>[size=1em]private [size=1em]void [size=1em]writeObject0(Object obj, [size=1em]boolean [size=1em]unshared) [size=1em]throws [size=1em]IOException {

[size=1em]    [size=1em]if [size=1em](obj [size=1em]instanceof [size=1em]String) {
[size=1em]        [size=1em]writeString((String) obj, unshared);
[size=1em]    [size=1em]} [size=1em]else [size=1em]if [size=1em](cl.isArray()) {
[size=1em]        [size=1em]writeArray(obj, desc, unshared);
[size=1em]    [size=1em]} [size=1em]else [size=1em]if [size=1em](obj [size=1em]instanceof [size=1em]Enum) {
[size=1em]        [size=1em]writeEnum((Enum) obj, desc, unshared);
[size=1em]    [size=1em]} [size=1em]else [size=1em]if [size=1em](obj [size=1em]instanceof [size=1em]Serializable) {
[size=1em]        [size=1em]writeOrdinaryObject(obj, desc, unshared);
[size=1em]    [size=1em]} [size=1em]else [size=1em]{
[size=1em]        [size=1em]if [size=1em](extendedDebugInfo) {
[size=1em]            [size=1em]throw [size=1em]new [size=1em]NotSerializableException(cl.getName() + [size=1em]"\n"
[size=1em]                    [size=1em]+ debugInfoStack.toString());
[size=1em]        [size=1em]} [size=1em]else [size=1em]{
[size=1em]            [size=1em]throw [size=1em]new [size=1em]NotSerializableException(cl.getName());
[size=1em]        [size=1em]}
[size=1em]    [size=1em]}

[size=1em]}</code>



从上述代码可知,如果被写对象的类型是String,或数组,或Enum,或Serializable,那么就可以对该对象进行序列化,否则将抛出NotSerializableException。
默认序列化机制
如果仅仅只是让某个类实现Serializable接口,而没有其它任何处理的话,则就是使用默认序列化机制。
默认的序列化机制,是对对象的所有成员变量(静态变量不会被序列化,因为它不属于某个对象,是所有对象共享的)进行序列化
使用默认机制,在序列化对象时,不仅会序列化当前对象本身,还会对该对象引用的其它对象也进行序列化,同样地,这些其它对象引用的另外对象也将被序列化,以此类推。所以,如果一个对象包含的成员变量是容器类对象,而这些容器所含有的元素也是容器类对象,那么这个序列化的过程就会较复杂,开销也较大。
影响序列化
在现实应用中,有些时候不能使用默认序列化机制。比如,希望在序列化过程中忽略掉敏感数据,或者简化序列化过程。下面将介绍若干影响序列化的方法。
transient关键字
有些情况下,是不希望对象的所有成员变量都进行序列化,比如User中的passwd字段,这个是敏感数据,不希望它被序列化,那么就可以使用transient关键字。
transient,顾名思义,非持久化的。使用transient关键字修饰成员变量,能够使它在序列化的过程中被忽略。
此处将Person类中的age字段声明为transient,如下所示,
?
1

2

3

4

5

[size=1em]<code [size=1em]class[size=1em]=[size=1em]"hljs java"[size=1em]>[size=1em]public [size=1em]class [size=1em]Person [size=1em]implements [size=1em]Serializable {

[size=1em]    [size=1em]transient [size=1em]private [size=1em]Integer age = [size=1em]null[size=1em];

[size=1em]}</code>



再执行SimpleSerial应用程序,会有如下输出:
?
1

2

[size=1em]<code [size=1em]class[size=1em]=[size=1em]"hljs delphi"[size=1em]>arg constructor
[size=1em][John, [size=1em]null[size=1em], MALE]</code>



* writeObject()方法与readObject()方法*
对于上述已被声明为transitive的字段age,除了将transitive关键字去掉之外,是否还有其它方法能使它再次可被序列化?方法之一就是在Person类中添加两个方法:writeObject()与readObject(),如下所示:
?
1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

[size=1em]<code [size=1em]class[size=1em]=[size=1em]"hljs java"[size=1em]>[size=1em]public [size=1em]class [size=1em]Person [size=1em]implements [size=1em]Serializable {

[size=1em]    [size=1em]transient [size=1em]private [size=1em]Integer age = [size=1em]null[size=1em];


[size=1em]    [size=1em]private [size=1em]void [size=1em]writeObject(ObjectOutputStream out) [size=1em]throws [size=1em]IOException {
[size=1em]        [size=1em]out.defaultWriteObject();
[size=1em]        [size=1em]out.writeInt(age);
[size=1em]    [size=1em]}

[size=1em]    [size=1em]private [size=1em]void [size=1em]readObject(ObjectInputStream in) [size=1em]throws [size=1em]IOException, ClassNotFoundException {
[size=1em]        [size=1em]in.defaultReadObject();
[size=1em]        [size=1em]age = in.readInt();
[size=1em]    [size=1em]}
[size=1em]}</code>



在writeObject()方法中会先调用ObjectOutputStream中的defaultWriteObject()方法,该方法会执行默认的序列化机制,如5.1节所述,此时会忽略掉age字段。然后再调用writeInt()方法显示地将age字段写入到ObjectOutputStream中。readObject()的作用则是针对对象的读取,其原理与writeObject()方法相同。
再次执行SimpleSerial应用程序,则又会有如下输出:
?
1

2

[size=1em]<code [size=1em]class[size=1em]=[size=1em]"hljs delphi"[size=1em]>arg constructor
[size=1em][John, [size=1em]31[size=1em], MALE]</code>



必须注意地是,writeObject()与readObject()都是private方法,那么它们是如何被调用的呢?毫无疑问,是使用反射。详情可见ObjectOutputStream中的writeSerialData方法,以及ObjectInputStream中的readSerialData方法。
通过这两个方法,可以对特殊要求的字段,增加额外的加密,解密的代码
Externalizable接口
以上例子都是基于实现Serializable接口来实现序列化的,Externalizable接口继承于Serializable,通过实现Externalizable接口也能实现序列化,不同的是,序列化操作的细节需要自己实现,而且,必须提供public的无参构造函数,否则会出现以下错误:

Java对象序列化使用基础和实例教程 b2b软件

无论是使用transient关键字,还是使用writeObject()和readObject()方法,其实都是基于Serializable接口的序列化。JDK中提供了另一个序列化接口–Externalizable,使用该接口之后,之前基于Serializable接口的序列化机制就将失效。此时将Person类修改成如下,
?
1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

[size=1em]<code [size=1em]class[size=1em]=[size=1em]"hljs java"[size=1em]>[size=1em]public [size=1em]class [size=1em]Person [size=1em]implements [size=1em]Externalizable {

[size=1em]    [size=1em]private [size=1em]String name = [size=1em]null[size=1em];

[size=1em]    [size=1em]transient [size=1em]private [size=1em]Integer age = [size=1em]null[size=1em];

[size=1em]    [size=1em]private [size=1em]Gender gender = [size=1em]null[size=1em];

[size=1em]    [size=1em]public [size=1em]Person() {
[size=1em]        [size=1em]System.out.println([size=1em]"none-arg constructor"[size=1em]);
[size=1em]    [size=1em]}

[size=1em]    [size=1em]public [size=1em]Person(String name, Integer age, Gender gender) {
[size=1em]        [size=1em]System.out.println([size=1em]"arg constructor"[size=1em]);
[size=1em]        [size=1em]this[size=1em].name = name;
[size=1em]        [size=1em]this[size=1em].age = age;
[size=1em]        [size=1em]this[size=1em].gender = gender;
[size=1em]    [size=1em]}

[size=1em]    [size=1em]private [size=1em]void [size=1em]writeObject(ObjectOutputStream out) [size=1em]throws [size=1em]IOException {
[size=1em]        [size=1em]out.defaultWriteObject();
[size=1em]        [size=1em]out.writeInt(age);
[size=1em]    [size=1em]}

[size=1em]    [size=1em]private [size=1em]void [size=1em]readObject(ObjectInputStream in) [size=1em]throws [size=1em]IOException, ClassNotFoundException {
[size=1em]        [size=1em]in.defaultReadObject();
[size=1em]        [size=1em]age = in.readInt();
[size=1em]    [size=1em]}

[size=1em]    [size=1em]@Override
[size=1em]    [size=1em]public [size=1em]void [size=1em]writeExternal(ObjectOutput out) [size=1em]throws [size=1em]IOException {

[size=1em]    [size=1em]}

[size=1em]    [size=1em]@Override
[size=1em]    [size=1em]public [size=1em]void [size=1em]readExternal(ObjectInput in) [size=1em]throws [size=1em]IOException, ClassNotFoundException {

[size=1em]    [size=1em]}

[size=1em]}</code>



此时再执行SimpleSerial程序之后会得到如下结果:
?
1

2

3

[size=1em]<code [size=1em]class[size=1em]=[size=1em]"hljs delphi"[size=1em]>arg constructor
[size=1em]none-arg constructor
[size=1em][[size=1em]null[size=1em], [size=1em]null[size=1em], [size=1em]null[size=1em]]</code>



从该结果,一方面可以看出Person对象中任何一个字段都没有被序列化。另一方面,如果细心的话,还可以发现这此次序列化过程调用了Person类的无参构造器。
另外,若使用Externalizable进行序列化,当读取对象时,会调用被序列化类的无参构造器去创建一个新的对象,然后再将被保存对象的字段的值分别填充到新对象中。这就是为什么在此次序列化过程中Person类的无参构造器会被调用。由于这个原因,实现Externalizable接口的类必须要提供一个无参的构造器,且它的访问权限为public。
对上述Person类作进一步的修改,使其能够对name与age字段进行序列化,但要忽略掉gender字段,如下代码所示:
?
1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

[size=1em]<code [size=1em]class[size=1em]=[size=1em]"hljs java"[size=1em]>[size=1em]public [size=1em]class [size=1em]Person [size=1em]implements [size=1em]Externalizable {

[size=1em]    [size=1em]private [size=1em]String name = [size=1em]null[size=1em];

[size=1em]    [size=1em]transient [size=1em]private [size=1em]Integer age = [size=1em]null[size=1em];

[size=1em]    [size=1em]private [size=1em]Gender gender = [size=1em]null[size=1em];

[size=1em]    [size=1em]public [size=1em]Person() {
[size=1em]        [size=1em]System.out.println([size=1em]"none-arg constructor"[size=1em]);
[size=1em]    [size=1em]}

[size=1em]    [size=1em]public [size=1em]Person(String name, Integer age, Gender gender) {
[size=1em]        [size=1em]System.out.println([size=1em]"arg constructor"[size=1em]);
[size=1em]        [size=1em]this[size=1em].name = name;
[size=1em]        [size=1em]this[size=1em].age = age;
[size=1em]        [size=1em]this[size=1em].gender = gender;
[size=1em]    [size=1em]}

[size=1em]    [size=1em]private [size=1em]void [size=1em]writeObject(ObjectOutputStream out) [size=1em]throws [size=1em]IOException {
[size=1em]        [size=1em]out.defaultWriteObject();
[size=1em]        [size=1em]out.writeInt(age);
[size=1em]    [size=1em]}

[size=1em]    [size=1em]private [size=1em]void [size=1em]readObject(ObjectInputStream in) [size=1em]throws [size=1em]IOException, ClassNotFoundException {
[size=1em]        [size=1em]in.defaultReadObject();
[size=1em]        [size=1em]age = in.readInt();
[size=1em]    [size=1em]}

[size=1em]    [size=1em]@Override
[size=1em]    [size=1em]public [size=1em]void [size=1em]writeExternal(ObjectOutput out) [size=1em]throws [size=1em]IOException {
[size=1em]        [size=1em]out.writeObject(name);
[size=1em]        [size=1em]out.writeInt(age);
[size=1em]    [size=1em]}

[size=1em]    [size=1em]@Override
[size=1em]    [size=1em]public [size=1em]void [size=1em]readExternal(ObjectInput in) [size=1em]throws [size=1em]IOException, ClassNotFoundException {
[size=1em]        [size=1em]name = (String) in.readObject();
[size=1em]        [size=1em]age = in.readInt();
[size=1em]    [size=1em]}

[size=1em]}</code>



执行SimpleSerial之后会有如下结果:
?
1

2

3

[size=1em]<code [size=1em]class[size=1em]=[size=1em]"hljs delphi"[size=1em]>arg constructor
[size=1em]none-arg constructor
[size=1em][John, [size=1em]31[size=1em], [size=1em]null[size=1em]]</code>



可知:采用这种方法实现序列化,transient是不起作用的,如果你不想序列化某个成员变量,只要在readExternal和writeExternal中不对该变量进行相应操作就可以了。
readResolve()方法
当我们使用Singleton模式时,应该是期望某个类的实例应该是唯一的,但如果该类是可序列化的,那么情况可能会略有不同。此时对第2节使用的Person类进行修改,使其实现Singleton模式,如下所示:
?
1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

[size=1em]<code [size=1em]class[size=1em]=[size=1em]"hljs java"[size=1em]>[size=1em]public [size=1em]class [size=1em]Person [size=1em]implements [size=1em]Serializable {

[size=1em]    [size=1em]private [size=1em]static [size=1em]class [size=1em]InstanceHolder {
[size=1em]        [size=1em]private [size=1em]static [size=1em]final [size=1em]Person instatnce = [size=1em]new [size=1em]Person([size=1em]"John"[size=1em], [size=1em]31[size=1em], Gender.MALE);
[size=1em]    [size=1em]}

[size=1em]    [size=1em]public [size=1em]static [size=1em]Person getInstance() {
[size=1em]        [size=1em]return [size=1em]InstanceHolder.instatnce;
[size=1em]    [size=1em]}

[size=1em]    [size=1em]private [size=1em]String name = [size=1em]null[size=1em];

[size=1em]    [size=1em]private [size=1em]Integer age = [size=1em]null[size=1em];

[size=1em]    [size=1em]private [size=1em]Gender gender = [size=1em]null[size=1em];

[size=1em]    [size=1em]private [size=1em]Person() {
[size=1em]        [size=1em]System.out.println([size=1em]"none-arg constructor"[size=1em]);
[size=1em]    [size=1em]}

[size=1em]    [size=1em]private [size=1em]Person(String name, Integer age, Gender gender) {
[size=1em]        [size=1em]System.out.println([size=1em]"arg constructor"[size=1em]);
[size=1em]        [size=1em]this[size=1em].name = name;
[size=1em]        [size=1em]this[size=1em].age = age;
[size=1em]        [size=1em]this[size=1em].gender = gender;
[size=1em]    [size=1em]}

[size=1em]}</code>



同时要修改SimpleSerial应用,使得能够保存/获取上述单例对象,并进行对象相等性比较,如下代码所示:
?
1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

[size=1em]<code [size=1em]class[size=1em]=[size=1em]"hljs cs"[size=1em]>[size=1em]public [size=1em]class [size=1em]SimpleSerial {

[size=1em]    [size=1em]public [size=1em]static [size=1em]void [size=1em]main(String[] args) [size=1em]throws [size=1em]Exception {
[size=1em]        [size=1em]File file = [size=1em]new [size=1em]File([size=1em]"person.out"[size=1em]);
[size=1em]        [size=1em]ObjectOutputStream oout = [size=1em]new [size=1em]ObjectOutputStream([size=1em]new [size=1em]FileOutputStream(file));
[size=1em]        [size=1em]oout.writeObject(Person.getInstance()); [size=1em]// 保存单例对象
[size=1em]        [size=1em]oout.close();

[size=1em]        [size=1em]ObjectInputStream oin = [size=1em]new [size=1em]ObjectInputStream([size=1em]new [size=1em]FileInputStream(file));
[size=1em]        [size=1em]Object newPerson = oin.readObject();
[size=1em]        [size=1em]oin.close();
[size=1em]        [size=1em]System.out.println(newPerson);

[size=1em]        [size=1em]System.out.println(Person.getInstance() == newPerson); [size=1em]// 将获取的对象与Person类中的单例对象进行相等性比较
[size=1em]    [size=1em]}
[size=1em]}</code>



执行上述应用程序后会得到如下结果:
?
1

2

3

[size=1em]<code [size=1em]class[size=1em]=[size=1em]"hljs delphi"[size=1em]>arg constructor
[size=1em][John, [size=1em]31[size=1em], MALE]
[size=1em]false[size=1em]</code>



值得注意的是,从文件person.out中获取的Person对象与Person类中的单例对象并不相等。为了能在序列化过程仍能保持单例的特性,可以在Person类中添加一个readResolve()方法,在该方法中直接返回Person的单例对象,如下所示:
?
1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

[size=1em]<code [size=1em]class[size=1em]=[size=1em]"hljs java"[size=1em]>[size=1em]public [size=1em]class [size=1em]Person [size=1em]implements [size=1em]Serializable {

[size=1em]    [size=1em]private [size=1em]static [size=1em]class [size=1em]InstanceHolder {
[size=1em]        [size=1em]private [size=1em]static [size=1em]final [size=1em]Person instatnce = [size=1em]new [size=1em]Person([size=1em]"John"[size=1em], [size=1em]31[size=1em], Gender.MALE);
[size=1em]    [size=1em]}

[size=1em]    [size=1em]public [size=1em]static [size=1em]Person getInstance() {
[size=1em]        [size=1em]return [size=1em]InstanceHolder.instatnce;
[size=1em]    [size=1em]}

[size=1em]    [size=1em]private [size=1em]String name = [size=1em]null[size=1em];

[size=1em]    [size=1em]private [size=1em]Integer age = [size=1em]null[size=1em];

[size=1em]    [size=1em]private [size=1em]Gender gender = [size=1em]null[size=1em];

[size=1em]    [size=1em]private [size=1em]Person() {
[size=1em]        [size=1em]System.out.println([size=1em]"none-arg constructor"[size=1em]);
[size=1em]    [size=1em]}

[size=1em]    [size=1em]private [size=1em]Person(String name, Integer age, Gender gender) {
[size=1em]        [size=1em]System.out.println([size=1em]"arg constructor"[size=1em]);
[size=1em]        [size=1em]this[size=1em].name = name;
[size=1em]        [size=1em]this[size=1em].age = age;
[size=1em]        [size=1em]this[size=1em].gender = gender;
[size=1em]    [size=1em]}

[size=1em]    [size=1em]private [size=1em]Object readResolve() [size=1em]throws [size=1em]ObjectStreamException {
[size=1em]        [size=1em]return [size=1em]InstanceHolder.instatnce;
[size=1em]    [size=1em]}

[size=1em]}</code>



再次执行本节的SimpleSerial应用后将有如下输出:
?
1

2

3

[size=1em]<code [size=1em]class[size=1em]=[size=1em]"hljs delphi"[size=1em]>arg constructor
[size=1em][John, [size=1em]31[size=1em], MALE]
[size=1em]true[size=1em]</code>



无论是实现Serializable接口,或是Externalizable接口,当从I/O流中读取对象时,readResolve()方法都会被调用到。实际上就是用readResolve()中返回的对象直接替换在反序列化过程中创建的对象,而被创建的对象则会被垃圾回收掉。
总结实现Serializable与Externalizable接口都可以实现序列化 前者实现方式如果不想序列化某个成员变量,使用transient关键字修饰该成员变量即可;如果想在此基础上添加一些自定义操作,在该类中实现writeObject与readObject方法(注意是private方法),在这两个方法里就可以做一些自定义操作,如改变某个成员变量的值。 后者实现方式,需要自己实现序列的细节(writeExternal与readExternal方法),并且必须提供一个public的无参构造函数。这种方式为自定义序列化提供了更多的灵活性。高级认识序列化 ID 问题
情境:
两个客户端 A 和 B 试图通过网络传递对象数据,A 端将对象 C 序列化为二进制数据再传给 B,B 反序列化得到 C。
问题:
C 对象的全类路径假设为 com.inout.Test,在 A 和 B 端都有这么一个类文件,功能代码完全一致。也都实现了 Serializable 接口,但是反序列化时总是提示不成功。
解决:
虚拟机是否允许反序列化,不仅取决于类路径和功能代码是否一致,一个非常重要的一点是两个类的序列化 ID 是否一致(就是 private static final long serialVersionUID = 1L)。清单 1 中,虽然两个类的功能代码完全一致,但是序列化 ID 不同,他们无法相互序列化和反序列化。
清单 1. 相同功能代码不同序列化 ID 的类对比
?
1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

[size=1em]<code [size=1em]class[size=1em]=[size=1em]"hljs java"[size=1em]> [size=1em]package [size=1em]com.inout;

[size=1em] [size=1em]import [size=1em]java.io.Serializable;

[size=1em] [size=1em]public [size=1em]class [size=1em]A [size=1em]implements [size=1em]Serializable {

[size=1em]     [size=1em]private [size=1em]static [size=1em]final [size=1em]long [size=1em]serialVersionUID = 1L;

[size=1em]     [size=1em]private [size=1em]String name;

[size=1em]     [size=1em]public [size=1em]String getName()
[size=1em]     [size=1em]{
[size=1em]         [size=1em]return [size=1em]name;
[size=1em]     [size=1em]}

[size=1em]     [size=1em]public [size=1em]void [size=1em]setName(String name)
[size=1em]     [size=1em]{
[size=1em]         [size=1em]this[size=1em].name = name;
[size=1em]     [size=1em]}
[size=1em] [size=1em]}

[size=1em] [size=1em]package [size=1em]com.inout;

[size=1em] [size=1em]import [size=1em]java.io.Serializable;

[size=1em] [size=1em]public [size=1em]class [size=1em]A [size=1em]implements [size=1em]Serializable {

[size=1em]     [size=1em]private [size=1em]static [size=1em]final [size=1em]long [size=1em]serialVersionUID = 2L;

[size=1em]     [size=1em]private [size=1em]String name;

[size=1em]     [size=1em]public [size=1em]String getName()
[size=1em]     [size=1em]{
[size=1em]         [size=1em]return [size=1em]name;
[size=1em]     [size=1em]}

[size=1em]     [size=1em]public [size=1em]void [size=1em]setName(String name)
[size=1em]     [size=1em]{
[size=1em]         [size=1em]this[size=1em].name = name;
[size=1em]</code>



如此问题中情景,客户端A将对象C序列化后,传给客户端B;客户端B对C进行反序列化时,B中必须有C对象对应的类,而且A、B客户端中对于对象C对应的类的序列化 ID 必须一致。
序列化 ID 在 Eclipse 下提供了两种生成策略,一个是固定的 1L,一个是随机生成一个不重复的 long 类型数据(实际上是使用 JDK 工具生成),在这里有一个建议,如果没有特殊需求,就是用默认的 1L 就可以,这样可以确保代码一致时反序列化成功。那么随机生成的序列化 ID 有什么作用呢,有些时候,通过改变序列化 ID 可以用来限制某些用户的使用。
序列化存储规则?
1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

[size=1em]<code [size=1em]class[size=1em]=[size=1em]"hljs avrasm"[size=1em]>ObjectOutputStream out = [size=1em]new [size=1em]ObjectOutputStream(
[size=1em]                    [size=1em]new [size=1em]FileOutputStream([size=1em]"result.obj"[size=1em]));
[size=1em]    [size=1em]Test test = [size=1em]new [size=1em]Test();
[size=1em]    [size=1em]//试图将对象两次写入文件
[size=1em]    [size=1em]out.writeObject(test);
[size=1em]    [size=1em]out.flush();
[size=1em]    [size=1em]System.out.println([size=1em]new [size=1em]File([size=1em]"result.obj"[size=1em]).length());
[size=1em]    [size=1em]out.writeObject(test);
[size=1em]    [size=1em]out.close();
[size=1em]    [size=1em]System.out.println([size=1em]new [size=1em]File([size=1em]"result.obj"[size=1em]).length());

[size=1em]    [size=1em]ObjectInputStream oin = [size=1em]new [size=1em]ObjectInputStream([size=1em]new [size=1em]FileInputStream(
[size=1em]            [size=1em]"result.obj"[size=1em]));
[size=1em]    [size=1em]//从文件依次读出两个文件
[size=1em]    [size=1em]Test t1 = (Test) oin.readObject();
[size=1em]    [size=1em]Test t2 = (Test) oin.readObject();
[size=1em]    [size=1em]oin.close();

[size=1em]    [size=1em]//判断两个引用是否指向同一个对象
[size=1em]    [size=1em]System.out.println(t1 == t2);
[size=1em]/*
[size=1em]31
[size=1em]36
[size=1em]true
[size=1em]*/

[size=1em]</code>



解答:Java 序列化机制为了节省磁盘空间,具有特定的存储规则,当写入文件的为同一对象时,并不会再将对象的内容进行存储,而只是再次存储一份引用,上面增加的 5 字节的存储空间就是新增引用和一些控制信息的空间。反序列化时,恢复引用关系,使得清单 3 中的 t1 和 t2 指向唯一的对象,二者相等,输出 true。该存储规则极大的节省了存储空间。


回复 支持 反对

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

相关导读
群发软件苹果AIWROK实例单选按钮组类[RadioButtonGroup]完整综合示例
苹果AIWROK实例单选按钮组类[RadioButtonGroup]完整综合示例
群发软件AIWROK软件苹果实例UI-垂直容器[Vertical]高级综合示例
AIWROK软件苹果实例UI-垂直容器[Vertical]高级综合示例
群发软件IOS苹果脚本View的完整功能实例
IOS苹果脚本View的完整功能实例
群发软件AIWROK苹果系统实例演示1标签类[Label]方法
AIWROK苹果系统实例演示1标签类[Label]方法
信息发布软件AIWROK软件苹果UI按钮Button方法示例
AIWROK软件苹果UI按钮Button方法示例
信息发布软件AIWROK软件苹果TAB界面视图示例
AIWROK软件苹果TAB界面视图示例
信息发布软件AIWROK苹果系统自带view视图简洁UI界面示例
AIWROK苹果系统自带view视图简洁UI界面示例
信息发布软件汇集HID安卓输入文字的方法和复制粘贴示例
汇集HID安卓输入文字的方法和复制粘贴示例
信息发布软件AIWROK软件找字与OCR方法汇总示例
AIWROK软件找字与OCR方法汇总示例
信息发布软件AIWROK软件找图方法汇总示例
AIWROK软件找图方法汇总示例
信息发布软件AIWROK软件滑动方法集合示例
AIWROK软件滑动方法集合示例
信息发布软件AIWROK软件安卓AIWROK汇集软件点击
AIWROK软件安卓AIWROK汇集软件点击
信息发布软件苹果系统点击方法综合示例
苹果系统点击方法综合示例
信息发布软件AIWROK苹果系统找图方法完整示例集合
AIWROK苹果系统找图方法完整示例集合
信息发布软件苹果系统找图方法完整示例集合
苹果系统找图方法完整示例集合
信息发布软件苹果IOS系统找字OCR方法例子
苹果IOS系统找字OCR方法例子
信息发布软件AIWORK软件数组高级示例
AIWORK软件数组高级示例
信息发布软件AIWROK软件运算符封装库示例
AIWROK软件运算符封装库示例
信息发布软件AIWROK软件语法运行小示例
AIWROK软件语法运行小示例
信息发布软件AIWROK软件JS循环小示例
AIWROK软件JS循环小示例
信息发布软件AIWROK软件H5网页被主脚本获取值用法
AIWROK软件H5网页被主脚本获取值用法
信息发布软件AIWROK软件创建可暂停恢复的多线程任务
AIWROK软件创建可暂停恢复的多线程任务
信息发布软件AIWROK软件类型转换方法例子
AIWROK软件类型转换方法例子
信息发布软件AIWROK软件H5脚本执行与进度显示
AIWROK软件H5脚本执行与进度显示 .
信息发布软件AIWROK软件根据时间段执行异步任务支持多线程并行处理
AIWROK软件根据时间段执行异步任务支持多线程并行处理
信息发布软件H5自动开关执行脚本功能演示
H5自动开关执行脚本功能演示
信息发布软件AIWROK软件H5单选脚本运行示例
AIWROK软件H5单选脚本运行示例
信息发布软件H5任务脚本选择与执行中心
H5任务脚本选择与执行中心
信息发布软件H5里CheckBox控件演示
H5里CheckBox控件演示
信息发布软件AIWROK软件正则用法实际例子
AIWROK软件正则用法实际例子
信息发布软件AIWROK软件权限管理器实现
AIWROK软件权限管理器实现
信息发布软件AIWORK软件节点方法无碍示例子
AIWORK软件节点方法无碍示例子
信息发布软件JSON.stringify 和 JSON.parse 完整示例
JSON.stringify 和 JSON.parse 完整示例
信息发布软件AIWROK软件展示JavaScript各种语句标识符的用法
AIWROK软件展示JavaScript各种语句标识符的用法
信息发布软件JS巧妙地组合使用各种条件语句
JS巧妙地组合使用各种条件语句
信息发布软件AIWROK手机数据库MySQL数据库截图片批量上传操作脚本
AIWROK手机数据库MySQL数据库截图片批量上传操作脚本
信息发布软件HID中文输入智能打字功能
HID中文输入智能打字功能
信息发布软件AIWROK软件对象工具函数库例子
AIWROK软件对象工具函数库例子
信息发布软件AIWROK软件H5交互演示黄色主题
AIWROK软件H5交互演示黄色主题
信息发布软件H5单按钮执行脚本示例
H5单按钮执行脚本示例
信息发布软件苹果H5界面完整调用脚本示例
苹果H5界面完整调用脚本示例
信息发布软件AIWROK软件平台设备信息全面检测工具例子
AIWROK软件平台设备信息全面检测工具例子
信息发布软件AIWROK创建和放大日志窗口并展示动态内容
AIWROK创建和放大日志窗口并展示动态内容
信息发布软件AIWROK软件device相关方法获取设备信息例子
AIWROK软件device相关方法获取设备信息例子[/backcolor]
信息发布软件数据库MySQL实时内容随机调用
数据库MySQL实时内容随机调用
信息发布软件AIWROK软件分享一个特效苹果H5页面
AIWROK软件分享一个特效苹果H5页面
信息发布软件数据库MYQ业务流程心跳程序启动
数据库MYQ业务流程心跳程序启动
信息发布软件数据库MySQL功能支持创建表插入中文数据查询删除功能例子
数据库MySQL功能支持创建表插入中文数据查询删除功能例子
信息发布软件AIWROK软件Zip 高级操作复杂示例
AIWROK软件Zip 高级操作复杂示例
信息发布软件AIWROK软件txt_文件读写方法小结
AIWROK软件txt_文件读写方法小结
信息发布软件AIWROK软件file文件操作方法小结
AIWROK软件file文件操作方法小结
信息发布软件AIWORK软件配置读写H5演示配套脚本
AIWORK软件配置读写H5演示配套脚本
信息发布软件AIWROK配置读写功能演示示例
AIWROK配置读写功能演示示例
信息发布软件AIWROK截图缓存工具
AIWROK截图缓存工具
信息发布软件AIWROK线程许可证工具
AIWROK线程许可证工具
信息发布软件整理了AIWROK环境下常用的Date对象和sleep对象方法
整理了AIWROK环境下常用的Date对象和sleep对象方法
信息发布软件FastUI界面普通用法
FastUI界面普通用法
信息发布软件FastUI界面类[window]方法小结
FastUI界面类[window]方法小结 方法 1:close(关闭指定窗口)方法 2:closeAll(关闭所有窗口)方法 3:loadUI(加载 UI 界面)方法 4:onClose(监听窗口关闭事件)方法 5:onLoad(监听窗口加载事件)方法 6:setFull(设置窗口全屏)方法 7:setHeight(设置窗口高度)方法 8:setHidden(隐藏窗口)方法 9:setLeft(设置窗口 X 轴坐标)方法 10:setTop(设置窗口 Y 轴坐标)方法 11:setVisable(显示隐藏的窗口)方
信息发布软件AIWROK软件按钮监听UI界面与事件监听功能演示
AIWROK软件按钮监听UI界面与事件监听功能演示.
信息发布软件AWIROK软件多选[uiCheckBox]方法小结
AWIROK软件多选方法小结 方法一:findByID 加载多选控件方法二:getAllChecked 获取所有选中项方法三:getAllSelect 获取所有选项方法四:getChecked 获取某个选项是否选中方法五:setChecked 设置某个选项是否选中方法六:setCheckeds 设置多个选项是否选中方法七:setHeight 设置高度
信息发布软件AIWROK日志演示开启日志显示 → 放大 → 关闭代码
AIWROK日志演示开启日志显示 → 放大 → 关闭代码
信息发布软件&#127983;AIWROK数组方法高级应用案例
🏯AIWROK数组方法高级应用案例
信息发布软件AIWROK软件日志悬浮窗简化版自动切换位置
AIWROK软件日志悬浮窗简化版自动切换位置
信息发布软件AIWROK软件String实例演示
AIWROK软件String实例演示
信息发布软件AIWROK软件S内置String类[String]方法小结
AIWROK软件S内置String类[String]方法小结 方法 1:charAt[/backcolor]方法 2:charCodeAt[/backcolor]方法 3:indexOf[/backcolor]方法 4:lastIndexOf[/backcolor]方法 5:length[/backcolor]方法 6:match[/backcolor]方法 7:replace[/backcolor]方法 8:replaceAll[/backcolor]方法 9:split[/backcolor]方法 10:startsWith[/backcolor]方法 11:substr[/backcolor]方法 12:substring[/backcolor]方法 13:trim[/backcol

QQ|( 京ICP备09078825号 )

本网站信息发布软件,是可以发布论坛,发送信息到各大博客,各大b2b软件自动发布,好不夸张的说:只要手工能发在电脑打开IE能发的网站,用这个宣传软件就可以仿制动作,进行推送发到您想发送的B2B网站或是信息发布平台上,不管是后台,还是前台,都可以进行最方便的广告发布,这个广告发布软件,可以按月购买,还可以试用软件,对网站的验证码也可以完全自动对信息发布,让客户自动找上门,使企业轻松实现b2b发布,这个信息发布软件,均是本站原创正版开发,拥有正版的血统,想要新功能,欢迎提意见给我,一好的分类信息群发软件在手,舍我其谁。QQ896757558

GMT+8, 2026-2-28 21:37 , Processed in 0.426207 second(s), 48 queries .

宣传软件--信息发布软件--b2b软件广告发布软件

快速回复 返回顶部 返回列表