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

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

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

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

(2)将对象或者异常等通过网络进行传输。

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

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

package test2;
 
import java.io.Serializable;
 
public class DataObject implements Serializable {
  
  /**
   * 序列化的UID号
   */
  private static final long serialVersionUID = -3737338076212523007L;
  
  public static int i =  0;
  private String word = "";
  
  public static void setI(int i){
    DataObject.i = i;
  }
  
  public void setWord(String word){
    this.word = word;
  }
  
  public static int getI() {
    return i;
  }
  public String getWord() {
    return word;
  }
 
  @Override
  public String toString() {
    return "word = " + word + ", " + "i = " + i;
  }
 
}

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

(1)类中有一个静态成员变量i,这个变量能不能被序列化呢?等下通过测试程序看一下;

(2)类中重写了toString方法,是为了打印结果。

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

package test2;
 
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
 
/**
 * Description: 测试对象的序列化和反序列
 */
public class TestObjSerializeAndDeserialize {
 
    public static void main(String[] args) throws Exception {
      
      // 序列化DataObject对象
        Serialize();
        
        // 反序列DataObject对象
        DataObject object = Deserialize();
        
        // 静态成员属于类级别的,所以不能序列化,序列化只是序列化了对象而已,
        // 这里的不能序列化的意思,是序列化信息中不包含这个静态成员域,下面
        // 之所以i输出还是2,是因为测试都在同一个机器(而且是同一个进程),因为这个jvm
        // 已经把i加载进来了,所以获取的是加载好的i,如果是传到另一台机器或者关掉程序重新
        // 写个程序读入DataObject.txt,此时因为别的机器或新的进程是重新加载i的,所以i信息就是初始时的信息,即0
        System.out.println(object);
    }
    
    /**
     * MethodName: SerializePerson 
     * Description: 序列化Person对象
     * @author 
     * @throws FileNotFoundException
     * @throws IOException
     */
    private static void Serialize() throws FileNotFoundException, IOException {
      
      DataObject object = new DataObject();
      object.setWord("123");
      object.setI(2);
      
        // 创建ObjectOutputStream对象输出流,其中用到了文件的描述符对象和文件输出流对象
        ObjectOutputStream oo = new ObjectOutputStream(new FileOutputStream(
                new File("DataObject.txt")));
        
        // 将DataObject对象存储到DataObject.txt文件中,完成对DataObject对象的序列化操作
        oo.writeObject(object);
        
        System.out.println("Person对象序列化成功!");
        
        // 最后一定记得关闭对象描述符!!!
        oo.close();
    }
 
    /**
     * MethodName: DeserializePerson 
     * Description: 反序列DataObject对象
     * @author 
     * @return
     * @throws Exception
     * @throws IOException
     */
    private static DataObject Deserialize() throws Exception, IOException {
      
      // 创建ObjectInputStream对象输入流,其中用到了文件的描述符对象和文件输入流对象 
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream(
                new File("DataObject.txt")));
        
        // 从DataObject.txt文件中读取DataObject对象,完成对DataObject对象的反序列化操作
        DataObject object = (DataObject) ois.readObject();
        System.out.println("Person对象反序列化成功!");
        
        // 最后一定记得关闭对象描述符!!!
        ois.close();
        
        return object;
    }
 
}

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

word = "123", i = 2

这样会使得大家觉得理应就是如此,其实这是错误的。大家要记住:

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

word = "123", i = 0

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

package test2;
 
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;
 
/**
 * Description: 测试对象的序列化
 */
public class SerializeDataobject {
 
    public static void main(String[] args) throws Exception {
      
      // 序列化DataObject对象
        Serialize();
        
    }
    
    /**
     * MethodName: SerializePerson 
     * Description: 序列化Person对象
     * @author 
     * @throws FileNotFoundException
     * @throws IOException
     */
    private static void Serialize() throws FileNotFoundException, IOException {
      
      DataObject object = new DataObject();
      object.setWord("123");
      object.setI(2);
      
        // 创建ObjectOutputStream对象输出流,其中用到了文件的描述符对象和文件输出流对象
        ObjectOutputStream oo = new ObjectOutputStream(new FileOutputStream(
                new File("DataObject.txt")));
        
        // 将DataObject对象存储到DataObject.txt文件中,完成对DataObject对象的序列化操作
        oo.writeObject(object);
        
        System.out.println("Person对象序列化成功!");
        
        // 最后一定记得关闭对象描述符!!!
        oo.close();
    }
}

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

package test2;
 
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
 
/**
 * Description: 测试对象的反序列
 */
public class DeserializeDataobject {
 
    public static void main(String[] args) throws Exception {
        
        // 反序列DataObject对象
        DataObject object = Deserialize();
        
        // 静态成员属于类级别的,所以不能序列化,序列化只是序列化了对象而已,
        // 这里的不能序列化的意思,是序列化信息中不包含这个静态成员域,下面
        // 之所以i输出还是2,是因为测试都在同一个机器(而且是同一个进程),因为这个jvm
        // 已经把i加载进来了,所以获取的是加载好的i,如果是传到另一台机器或者关掉程序重新
        // 写个程序读入DataObject.txt,此时因为别的机器或新的进程是重新加载i的,所以i信息就是初始时的信息,即0
        System.out.println(object);
    }
 
    /**
     * MethodName: DeserializePerson 
     * Description: 反序列DataObject对象
     * @author 
     * @return
     * @throws Exception
     * @throws IOException
     */
    private static DataObject Deserialize() throws Exception, IOException {
      
      // 创建ObjectInputStream对象输入流,其中用到了文件的描述符对象和文件输入流对象 
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream(
                new File("DataObject.txt")));
        
        // 从DataObject.txt文件中读取DataObject对象,完成对DataObject对象的反序列化操作
        DataObject object = (DataObject) ois.readObject();
        System.out.println("Person对象反序列化成功!");
        
        // 最后一定记得关闭对象描述符!!!
        ois.close();
        
        return object;
    }
 
}

上面这段程序用来实现对象的反序列化,它从文件"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方法。

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

本文转自:https://blog.csdn.net/dan15188387481/article/details/49977421

原文发布于微信公众号 - Spark学习技巧(bigdatatip)

原文发表时间:2018-07-23

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏熊二哥

快速入门系列--CLR--03泛型集合

.NET中的泛型集合 在这里主要介绍常见的泛型集合,很多时候其并发时的线程安全性常常令我们担忧。因而简述下.NET并发时线程安全特性,其详情请见MSDN。 ...

1787
来自专栏PhpZendo

PHP 垃圾回收与内存管理指引

php 的变量存储在「zval」变量容器(数据结构)中,「zval」属性包含如下信息:

1620
来自专栏何俊林

面试常问点:深入剖析JVM的那些事

Class文件结构 -> JVM内存模型 -> 类加载器 -> 类加载过程 -> 类的引用方式 -> 内存分配策略 -> GC -> 对象的引用类型 -> 类卸...

1042
来自专栏JAVA高级架构

Java常见面试题及答案 11-20(JVM)

11.JVM内存分哪几个区,每个区的作用是什么? java虚拟机主要分为以下一个区: 方法区: 1. 有时候也成为永久代,在该区内很少发生垃圾回收,但是并不代表...

5279
来自专栏Java3y

JVM如何从入门到放弃的?

JVM在准备面试的时候就有看了,一直没时间写笔记。现在到了一家公司实习,闲的时候就写写,刷刷JVM博客,刷刷电子书。

2860
来自专栏技术博客

C#简单的面试题目(五)

61.数组有没有length()这个方法? String有没有length()这个方法?

1143
来自专栏Java成长之路

三、JVM之对象的创建

上篇博文中已经介绍过了jvm内存的概况,接下来我们从jvm的角度来重新来认识一下Java对象是如何创建。 Java是一门面向对象的语言,在Java程序运行的...

942
来自专栏互联网杂技

javascript垃圾收集机制与内存泄漏详解

javascript具有自动垃圾收集机制,也就是说,执行环境会负责管理代码执行过程中的使用的内存。而在C和C++之类的语言中,开发人员的一项基本任务就是手动跟踪...

40010
来自专栏java思维导图

JVM理解其实并不难!

在阅读本文之前,先向大家强烈推荐一下周志明的《深入理解 Java 虚拟机》这本书。 前些天面试了阿里的实习生,问到关于 Dalvik 虚拟机能不能执行 clas...

2804
来自专栏小狼的世界

Linux下不同文件编码的转换

字符编码(Character Encoding)可以说就是让某一字符序列匹配一个指定集合中的某一东西,常见的例子包括长短电键组合起来表示的摩斯电码(Morse ...

1092

扫码关注云+社区

领取腾讯云代金券