Java 中的类为什么要实现序列化呢 / JAVA中序列化和反序列化中的静态成员问题

来源:互联网 发布:优化发展环境心得体会 编辑:程序博客网 时间:2024/05/16 23:52
java学习与交流2017-08-28 12:18

很多人觉得自己写得 Java 代码中,新建的 pojo 对象要实现序列化是为了要保存到硬盘上,其实呢,实现序列化和保存到硬盘上没有必然的关系。

以下图举例:

Java 中的类为什么要实现序列化呢

假设左边的是你的电脑,也就是客户端,右边的是服务器。之前你的客户端和服务器可能都在同一个电脑上,都是 Windows 下,那么右边的服务器也可以放到 Linux 中,这就涉及到左右两个不同的服务器了。中间用一条竖线分隔一下。

客户端可以调用服务器,所以肯定要传递参数。假设你传递的是字符串,没有问题,所有的机器都可以识别正常的字符串。

那么现在假设你传递的参数是一个 Java 对象,比如叫 cat。服务器并没有那么智能,它并不会知道你传递的是一个 Java 对象,而不是其他类型的数据,它识别不了 Java 对象。

Java 对象本质上是 class 字节码,服务器并不能根据这个字节码识别出该 Java 对象。所以,要提供一个公共的格式,不仅 Windows 能识别,你的服务器也能识别的公共的格式。

我们将 Java 对象转换成公共的格式叫做序列化,将公共的格式转换成对象叫做反序列化。保存到磁盘只是序列化的一种表现形式。

就这么简单,小小的问题,希望对大家有所帮助。

学习过程中遇到什么问题或者想获取学习资源的话,欢迎加入Java学习交流群346942462,我们一起学Java!


JAVA中序列化和反序列化中的静态成员问题


关于这个标题的内容是面试笔试中比较常见的考题,大家跟随我的博客一起来学习下这个过程。


    JAVA中的序列化和反序列化主要用于:

(1)将对象或者异常等写入文件,通过文件交互传输信息;
(2)将对象或者异常等通过网络进行传输。

    那么为什么需要序列化和反序列化呢?简单来说,如果你只是自己同一台机器的同一个环境下使用同一个JVM来操作,序列化和反序列化是没必要的,当需要进行数据传输的时候就显得十分必要。比如你的数据写到文件里要被其他人的电脑的程序使用,或者你电脑上的数据需要通过网络传输给其他人的程序使用,像服务器客户端的这种模型就是一种应用,这个时候,大家想想,每个人的电脑配置可能不同,运行环境可能也不同,字节序可能也不同,总之很多地方都不能保证一致,所以为了统一起见,我们传输的数据或者经过文件保存的数据需要经过序列化和编码等操作,相当于交互双方有一个公共的标准,按照这种标准来做,不管各自的环境是否有差异,各自都可以根据这种标准来翻译出自己能理解的正确的数据。

    在JAVA中有专门用于此类操作的API,供开发者直接使用,对象的序列化和反序列化可以通过将对象实现Serializable接口,然后用对象的输入输出流进行读写,下面看一个完整的例子。

[java] view plain copy
  1. package test2;  
  2.   
  3. import java.io.Serializable;  
  4.   
  5. public class DataObject implements Serializable {  
  6.       
  7.     /** 
  8.      * 序列化的UID号 
  9.      */  
  10.     private static final long serialVersionUID = -3737338076212523007L;  
  11.       
  12.     public static int i =  0;  
  13.     private String word = "";  
  14.       
  15.     public static void setI(int i){  
  16.         DataObject.i = i;  
  17.     }  
  18.       
  19.     public void setWord(String word){  
  20.         this.word = word;  
  21.     }  
  22.       
  23.     public static int getI() {  
  24.         return i;  
  25.     }  
  26.     public String getWord() {  
  27.         return word;  
  28.     }  
  29.   
  30.     @Override  
  31.     public String toString() {  
  32.         return "word = " + word + ", " + "i = " + i;  
  33.     }  
  34.   
  35. }  

    上面这段程序是定义了要被序列化和反序列化的类DataObject,这个类实现了Serializable接口,里面有几点需要注意:

(1)类中有一个静态成员变量i,这个变量能不能被序列化呢?等下通过测试程序看一下;
(2)类中重写了toString方法,是为了打印结果。

    接下来我们看一下测试该类的对象序列化和反序列化的一个测试程序版本,提前说明,这个版本是有问题的。

[java] view plain copy
  1. package test2;  
  2.   
  3. import java.io.File;  
  4. import java.io.FileInputStream;  
  5. import java.io.FileNotFoundException;  
  6. import java.io.FileOutputStream;  
  7. import java.io.IOException;  
  8. import java.io.ObjectInputStream;  
  9. import java.io.ObjectOutputStream;  
  10.   
  11. /** 
  12.  * Description: 测试对象的序列化和反序列 
  13.  */  
  14. public class TestObjSerializeAndDeserialize {  
  15.   
  16.     public static void main(String[] args) throws Exception {  
  17.           
  18.         // 序列化DataObject对象  
  19.         Serialize();  
  20.           
  21.         // 反序列DataObject对象  
  22.         DataObject object = Deserialize();  
  23.           
  24.         // 静态成员属于类级别的,所以不能序列化,序列化只是序列化了对象而已,  
  25.         // 这里的不能序列化的意思,是序列化信息中不包含这个静态成员域,下面  
  26.         // 之所以i输出还是2,是因为测试都在同一个机器(而且是同一个进程),因为这个jvm  
  27.         // 已经把i加载进来了,所以获取的是加载好的i,如果是传到另一台机器或者关掉程序重新  
  28.         // 写个程序读入DataObject.txt,此时因为别的机器或新的进程是重新加载i的,所以i信息就是初始时的信息,即0  
  29.         System.out.println(object);  
  30.     }  
  31.       
  32.     /** 
  33.      * MethodName: SerializePerson  
  34.      * Description: 序列化Person对象 
  35.      * @author  
  36.      * @throws FileNotFoundException 
  37.      * @throws IOException 
  38.      */  
  39.     private static void Serialize() throws FileNotFoundException, IOException {  
  40.           
  41.         DataObject object = new DataObject();  
  42.         object.setWord("123");  
  43.         object.setI(2);  
  44.           
  45.         // 创建ObjectOutputStream对象输出流,其中用到了文件的描述符对象和文件输出流对象  
  46.         ObjectOutputStream oo = new ObjectOutputStream(new FileOutputStream(  
  47.                 new File("DataObject.txt")));  
  48.           
  49.         // 将DataObject对象存储到DataObject.txt文件中,完成对DataObject对象的序列化操作  
  50.         oo.writeObject(object);  
  51.           
  52.         System.out.println("Person对象序列化成功!");  
  53.           
  54.         // 最后一定记得关闭对象描述符!!!  
  55.         oo.close();  
  56.     }  
  57.   
  58.     /** 
  59.      * MethodName: DeserializePerson  
  60.      * Description: 反序列DataObject对象 
  61.      * @author  
  62.      * @return 
  63.      * @throws Exception 
  64.      * @throws IOException 
  65.      */  
  66.     private static DataObject Deserialize() throws Exception, IOException {  
  67.           
  68.         // 创建ObjectInputStream对象输入流,其中用到了文件的描述符对象和文件输入流对象   
  69.         ObjectInputStream ois = new ObjectInputStream(new FileInputStream(  
  70.                 new File("DataObject.txt")));  
  71.           
  72.         // 从DataObject.txt文件中读取DataObject对象,完成对DataObject对象的反序列化操作  
  73.         DataObject object = (DataObject) ois.readObject();  
  74.         System.out.println("Person对象反序列化成功!");  
  75.           
  76.         // 最后一定记得关闭对象描述符!!!  
  77.         ois.close();  
  78.           
  79.         return object;  
  80.     }  
  81.   
  82. }  

    上面这段程序大家可以直接运行。注意,这里定义了两个方法Serialize()和Deserialize(),分别实现了序列化和反序列化的功能,里面的主要用到了对象输入输出流和文件输入输出流,大家看一下程序中的注释就可以理解。在序列化的方法中,将对象的成员变量word设置成了"123",i设置成了"2",注意这里的i是静态变量,那么以通常的序列化和反序列化的理解来看,无非就是一个正过程和一个逆过程,最终经过反序列化后,输出对象中的word和i时,大家一般都觉得应该还是"123"和"2",那么上面程序的运行结果确实就是:

[java] view plain copy
  1. word = "123", i = 2  
     
    这样会使得大家觉得理应就是如此,其实这是错误的。大家要记住: 
    
    静态成员属于类级别的,所以不能序列化,序列化只是序列化了对象而已,这里“不能序列化”的意思是序列化信息中不包含这个静态成员域,下面之所以i输出还是2,是因为测试都在同一个机器(而且是同一个进程),因为这个jvm已经把i加载进来了,所以获取的是加载好的i,如果是传到另一台机器或者关掉程序重新写个程序读入DataObject.txt,此时因为别的机器或新的进程是重新加载i的,所以i信息就是初始时的信息,即0。所以,总结来看,静态成员是不能被序列化的,静态成员定以后的默认初始值是0,所以正确的运行结果应该是:
[java] view plain copy
  1. word = "123", i = 0  

    那么既然如此,怎样才能测试出正确的结果呢?大家注意,上面的程序是直接在一个JVM一个进程中操作完了序列化和反序列化的所有过程,故而JVM中已经保存了i = 2,所以i的值没有变化,所以再次读出来肯定还是2。如果想得出正确的结果,必须在两个JVM中去测试,但是大家的电脑很难做到这种测试环境,所以可以通过以下方法来测试。

[java] view plain copy
  1. package test2;  
  2.   
  3. import java.io.File;  
  4. import java.io.FileNotFoundException;  
  5. import java.io.FileOutputStream;  
  6. import java.io.IOException;  
  7. import java.io.ObjectOutputStream;  
  8.   
  9. /** 
  10.  * Description: 测试对象的序列化 
  11.  */  
  12. public class SerializeDataobject {  
  13.   
  14.     public static void main(String[] args) throws Exception {  
  15.           
  16.         // 序列化DataObject对象  
  17.         Serialize();  
  18.           
  19.     }  
  20.       
  21.     /** 
  22.      * MethodName: SerializePerson  
  23.      * Description: 序列化Person对象 
  24.      * @author  
  25.      * @throws FileNotFoundException 
  26.      * @throws IOException 
  27.      */  
  28.     private static void Serialize() throws FileNotFoundException, IOException {  
  29.           
  30.         DataObject object = new DataObject();  
  31.         object.setWord("123");  
  32.         object.setI(2);  
  33.           
  34.         // 创建ObjectOutputStream对象输出流,其中用到了文件的描述符对象和文件输出流对象  
  35.         ObjectOutputStream oo = new ObjectOutputStream(new FileOutputStream(  
  36.                 new File("DataObject.txt")));  
  37.           
  38.         // 将DataObject对象存储到DataObject.txt文件中,完成对DataObject对象的序列化操作  
  39.         oo.writeObject(object);  
  40.           
  41.         System.out.println("Person对象序列化成功!");  
  42.           
  43.         // 最后一定记得关闭对象描述符!!!  
  44.         oo.close();  
  45.     }  
  46. }  

    上面这个类只用来进行序列化,对象被序列化后保存在文件"DataObject.txt"中,然后程序运行结束,JVM退出。接下来看另一段程序。

[java] view plain copy
  1. package test2;  
  2.   
  3. import java.io.File;  
  4. import java.io.FileInputStream;  
  5. import java.io.IOException;  
  6. import java.io.ObjectInputStream;  
  7.   
  8. /** 
  9.  * Description: 测试对象的反序列 
  10.  */  
  11. public class DeserializeDataobject {  
  12.   
  13.     public static void main(String[] args) throws Exception {  
  14.           
  15.         // 反序列DataObject对象  
  16.         DataObject object = Deserialize();  
  17.           
  18.         // 静态成员属于类级别的,所以不能序列化,序列化只是序列化了对象而已,  
  19.         // 这里的不能序列化的意思,是序列化信息中不包含这个静态成员域,下面  
  20.         // 之所以i输出还是2,是因为测试都在同一个机器(而且是同一个进程),因为这个jvm  
  21.         // 已经把i加载进来了,所以获取的是加载好的i,如果是传到另一台机器或者关掉程序重新  
  22.         // 写个程序读入DataObject.txt,此时因为别的机器或新的进程是重新加载i的,所以i信息就是初始时的信息,即0  
  23.         System.out.println(object);  
  24.     }  
  25.   
  26.     /** 
  27.      * MethodName: DeserializePerson  
  28.      * Description: 反序列DataObject对象 
  29.      * @author  
  30.      * @return 
  31.      * @throws Exception 
  32.      * @throws IOException 
  33.      */  
  34.     private static DataObject Deserialize() throws Exception, IOException {  
  35.           
  36.         // 创建ObjectInputStream对象输入流,其中用到了文件的描述符对象和文件输入流对象   
  37.         ObjectInputStream ois = new ObjectInputStream(new FileInputStream(  
  38.                 new File("DataObject.txt")));  
  39.           
  40.         // 从DataObject.txt文件中读取DataObject对象,完成对DataObject对象的反序列化操作  
  41.         DataObject object = (DataObject) ois.readObject();  
  42.         System.out.println("Person对象反序列化成功!");  
  43.           
  44.         // 最后一定记得关闭对象描述符!!!  
  45.         ois.close();  
  46.           
  47.         return object;  
  48.     }  
  49.   
  50. }  

    上面这段程序用来实现对象的反序列化,它从文件"DataObject.txt"中读出对象的相关信息,然后进行了反序列化,最终输出对象中word和i的值,这个程序输出的结果才是word = "123", i = 0 这个才是正确的结果,这是因为序列化和反序列化都有自己的main方法,先序列化,然后JVM退出,再次运行反序列化,JVM重新加载DataObject类,此时i = 0,"DataObject.txt"文件中其实是没有i的信息的,只有word的信息。这里通过先后执行序列化和反序列化,让JVM得到一次重新加载类的机会,模拟了两个JVM下运行的结果。

    总之,大家要记住以下几点:

(1)序列化和反序列化的实现方法和应用场合;
(2)静态成员是不能被序列化的,因为静态成员是随着类的加载而加载的,与类共存亡,并且静态成员的默认初始值都是0;
(3)要明白错误的那个测试程序的原因,搞明白JVM的一些基本机制;
(4)要想直接通过打印对象而输出对象的一些属性信息,要重写toString方法。

   上面只是我的一些个人总结,欢迎大家指正和补充。

原创粉丝点击