深入理解java序列化机制

序列化是指对象通过写出描述自己状态的数值来记录自己的过程,即将对象表示成一系列有序字节,Java提供了将对象写入流和从流中恢复对象的方法。对象能包含其它的对象,而其它的对象又可以包含另外的对象。Java序列化能够自动的处理嵌套的对象。对于一个对象的简单域,writeObject()直接将其值写入流中。当遇到一个对象域时,writeObject()被再次调用,如果这个对象内嵌另一个对象,那么,writeObject()又被调用,直到对象能被直接写入流为止。程序员所需要做的是将对象传入ObjectOutputStream的writeObject()方法,剩下的将有系统自动完成。

要实现序列化的类必须实现的java.io.Serializable或java.io.Externalizable接口,否则将产生一个NotSerializableException。该接口内部并没有任何方法,它只是一个"tagging interface",仅仅"tags"它自己的对象是一个特殊的类型。类通过实现 java.io.Serializable接口以启用其序列化功能。未实现此接口的类将无法使其任何状态序列化或反序列化。可序列化类的所有子类型本身都是可序列化的。序列化接口没有方法或字段,仅用于标识可序列化的语义。Java的"对象序列化"能让你将一个实现了Serializable接口的对象转换成一组byte,这样日后要用这个对象时候,你就能把这些byte数据恢复出来,并据此重新构建那个对象了。

2、序列化必要性及目的

Java中,一切都是对象,在分布式环境中经常需要将Object从这一端网络或设备传递到另一端。这就需要有一种可以在两端传输数据的协议。Java序列化机制就是为了解决这个问题而产生。

Java序列化支持的两种主要特性:

  • Java 的RMI使本来存在于其他机器的对象可以表现出就象本地机器上的行为。
  • 将消息发给远程对象时,需要通过对象序列化来传输参数和返回值

Java序列化的目的(我目前能理解的):

  • 支持运行在不同虚拟机上不同版本类之间的双向通讯;
  • 提供对持久性和RMI的序列化;

3、关于序列化的一些例子

下面我们通过一个简单的例子来看下Java默认支持的序列化。我们先定义一个类,然后将其序列化到文件中,最后读取文件重新构建出这个对象。在序列化一个对象的时候,有几点需要注意下:

  • 当一个对象被序列化时,只序列化对象的非静态成员变量,不能序列化任何成员方法和静态成员变量。
  • 如果一个对象的成员变量是一个对象,那么这个对象的数据成员也会被保存。
  • 如果一个可序列化的对象包含对某个不可序列化的对象的引用,那么整个序列化操作将会失败,并且会抛出一个NotSerializableException。可以通过将这个引用标记为transient,那么对象仍然可以序列化。对于一些比较敏感的不想序列化的数据,也可以采用该标识进行修饰。 
    下面我们先通过一个简单的例子来看一下Java内置的序列化过程。
<span style="color:#444444"><span style="color:#333333"><strong>class</strong></span> <span style="color:#880000"><strong>SuperClass</strong></span> <span style="color:#333333"><strong>implements</strong></span> <span style="color:#880000"><strong>Serializable</strong></span>{
    <span style="color:#333333"><strong>private</strong></span> String name;
    <span style="color:#333333"><strong>private</strong></span> <span style="color:#333333"><strong>int</strong></span> age;
    <span style="color:#333333"><strong>private</strong></span> String email;
    
    <span style="color:#333333"><strong>public</strong></span> String <span style="color:#880000"><strong>getName</strong></span>() {
    	<span style="color:#333333"><strong>return</strong></span> name;
    }
    
    <span style="color:#333333"><strong>public</strong></span> <span style="color:#333333"><strong>int</strong></span> <span style="color:#880000"><strong>getAge</strong></span>() {
    	<span style="color:#333333"><strong>return</strong></span> age;
    }
    
    <span style="color:#333333"><strong>public</strong></span> String <span style="color:#880000"><strong>getEmail</strong></span>() {
    	<span style="color:#333333"><strong>return</strong></span> email;
    }
    
    <span style="color:#333333"><strong>public</strong></span> <span style="color:#880000"><strong>SuperClass</strong></span>(String name,<span style="color:#333333"><strong>int</strong></span> age,String email) {
    	<span style="color:#333333"><strong>this</strong></span>.name=name;
    	<span style="color:#333333"><strong>this</strong></span>.age=age;
    	<span style="color:#333333"><strong>this</strong></span>.email=email;
    }
}
复制代码</span>

下面我们来看下main方法里面的序列化过程,代码如下:

<span style="color:#444444"><span style="color:#333333"><strong>public</strong></span> <span style="color:#333333"><strong>static</strong></span> <span style="color:#333333"><strong>void</strong></span> main(String[] args) <span style="color:#333333"><strong>throws</strong></span> IOException,ClassNotFoundException {
    	System.out.<span style="color:#333333"><strong>println</strong></span>(<span style="color:#880000">"序列化对象开始!"</span>);
    	SuperClass superClass=<span style="color:#333333"><strong>new</strong></span> SuperClass(<span style="color:#880000">"gong"</span>,<span style="color:#880000">27</span>, <span style="color:#880000">"1301334028@qq.com"</span>);
    	<span style="color:#333333"><strong>File</strong></span> rootfile=<span style="color:#333333"><strong>new</strong></span> <span style="color:#333333"><strong>File</strong></span>(<span style="color:#880000">"C:/data"</span>);
    	<span style="color:#333333"><strong>if</strong></span>(!rootfile.exists()) {
    		rootfile.mkdirs();
    	}
    	<span style="color:#333333"><strong>File</strong></span> <span style="color:#333333"><strong>file</strong></span>=<span style="color:#333333"><strong>new</strong></span> <span style="color:#333333"><strong>File</strong></span>(<span style="color:#880000">"C:/data/data.txt"</span>);
    	<span style="color:#333333"><strong>if</strong></span>(!<span style="color:#333333"><strong>file</strong></span>.exists()) {
    		<span style="color:#333333"><strong>file</strong></span>.createNewFile();
    	}
    	FileOutputStream fileOutputStream=<span style="color:#333333"><strong>new</strong></span> FileOutputStream(<span style="color:#333333"><strong>file</strong></span>);
    	ObjectOutputStream objectOutputStream=<span style="color:#333333"><strong>new</strong></span> ObjectOutputStream(fileOutputStream);
    	objectOutputStream.writeObject(superClass);
    	objectOutputStream.flush();
    	objectOutputStream.close();
    	System.out.<span style="color:#333333"><strong>println</strong></span>(<span style="color:#880000">"序列化对象完成!"</span>);
    	
    	System.out.<span style="color:#333333"><strong>println</strong></span>(<span style="color:#880000">"反序列化对象开始!"</span>);
    	FileInputStream fileInputStream=<span style="color:#333333"><strong>new</strong></span> FileInputStream(<span style="color:#333333"><strong>new</strong></span> <span style="color:#333333"><strong>File</strong></span>(<span style="color:#880000">"C:\\data\\data.txt"</span>));
    	ObjectInputStream objectInputStream=<span style="color:#333333"><strong>new</strong></span> ObjectInputStream(fileInputStream);
    	SuperClass getObject=(SuperClass) objectInputStream.readObject();
    	System.out.<span style="color:#333333"><strong>println</strong></span>(<span style="color:#880000">"反序列化对象数据:"</span>);
    	
    	System.out.<span style="color:#333333"><strong>println</strong></span>(<span style="color:#880000">"name:"</span>+getObject.getName()+<span style="color:#880000">"\nage:"</span>+getObject.getAge()+<span style="color:#880000">"\nemail:"</span>+getObject.getEmail());
}
复制代码</span>

代码运行结果如下:

<span style="color:#444444">序列化对象开始!
序列化对象完成!
反序列化对象开始!
反序列化对象数据:
<span style="color:#333333"><strong>name</strong></span><span style="color:#bc6060">:gong</span>
<span style="color:#333333"><strong>age</strong></span><span style="color:#bc6060">:27</span>
<span style="color:#333333"><strong>email</strong></span><span style="color:#bc6060">:1301334028</span>@<span style="color:#333333"><strong>qq</strong></span>.<span style="color:#333333"><strong>com</strong></span>
复制代码</span>

通过上面的例子,我们看到Java默认提供了序列化与反序列化机制,对于单个实体类来说,整个过程都是自动完成的,无需程序员进行额外的干预。如果我们想让某些关键的域不参与序列化过程呢?Java提供了方法,接着往下看。

transient关键字与序列化

如果我们现在想让上面SuperClass类走age和email不参与序列化过程,那么只需要在其定义前面加上transient关键字即可:

<span style="color:#444444"><span style="color:#333333"><strong>private</strong></span> <span style="color:#333333"><strong>transient</strong></span> <span style="color:#333333"><strong>int</strong></span> age;
<span style="color:#333333"><strong>private</strong></span> <span style="color:#333333"><strong>transient</strong></span> String email;
复制代码</span>

这样我们在进行序列化的时候,字节流中不不包含age和email的数据的,反序列的时候会赋予这两个变量默认值。还是运行刚才的工程,这时候我们结果如下:

<span style="color:#444444">序列化对象开始!
序列化对象完成!
反序列化对象开始!
反序列化对象数据:
name:gong
age:<span style="color:#880000">0</span>
email:<span style="color:#78a960">null</span>
复制代码</span>

自定义序列化过程

如果默认的序列化过程不能满足需求,我们也可以自定义整个序列化过程。这时候我们只需要在需要序列化的类中定义writeObject方法和readObject方法即可。我们还是以SuperClass为例,现在我们添加自定义的序列化过程,transient关键字让Java内置的序列化过程忽略修饰的变量,我们通过自定义序列化过程,还是序列化age和email,我们来看看改动后的结果:

<span style="color:#444444"><span style="color:#333333"><strong>private</strong></span> String name;
<span style="color:#333333"><strong>private</strong></span> <span style="color:#333333"><strong>transient</strong></span> <span style="color:#333333"><strong>int</strong></span> age;
<span style="color:#333333"><strong>private</strong></span> <span style="color:#333333"><strong>transient</strong></span> String email;

<span style="color:#333333"><strong>public</strong></span> String <span style="color:#880000"><strong>getName</strong></span>() {
	<span style="color:#333333"><strong>return</strong></span> name;
}

<span style="color:#333333"><strong>public</strong></span> <span style="color:#333333"><strong>int</strong></span> <span style="color:#880000"><strong>getAge</strong></span>() {
	<span style="color:#333333"><strong>return</strong></span> age;
}

<span style="color:#333333"><strong>public</strong></span> String <span style="color:#880000"><strong>getEmail</strong></span>() {
	<span style="color:#333333"><strong>return</strong></span> email;
}

<span style="color:#333333"><strong>public</strong></span> <span style="color:#880000"><strong>SuperClass</strong></span>(String name,<span style="color:#333333"><strong>int</strong></span> age,String email) {
	<span style="color:#333333"><strong>this</strong></span>.name=name;
	<span style="color:#333333"><strong>this</strong></span>.age=age;
	<span style="color:#333333"><strong>this</strong></span>.email=email;
}

<span style="color:#333333"><strong>private</strong></span> <span style="color:#333333"><strong>void</strong></span> <span style="color:#880000"><strong>writeObject</strong></span>(ObjectOutputStream objectOutputStream) 
		<span style="color:#333333"><strong>throws</strong></span> IOException {
	objectOutputStream.defaultWriteObject();
	objectOutputStream.writeInt(age);
	objectOutputStream.writeObject(email);
}


<span style="color:#333333"><strong>private</strong></span> <span style="color:#333333"><strong>void</strong></span> <span style="color:#880000"><strong>readObject</strong></span>(ObjectInputStream objectInputStream) 
		<span style="color:#333333"><strong>throws</strong></span> ClassNotFoundException,IOException {
	objectInputStream.defaultReadObject();
	age=objectInputStream.readInt();
	email=(String)objectInputStream.readObject();
}
复制代码</span>

运行结果如下:

<span style="color:#444444">反序列化对象数据:
<span style="color:#333333"><strong>name</strong></span><span style="color:#bc6060">:gong</span>
<span style="color:#333333"><strong>age</strong></span><span style="color:#bc6060">:27</span>
<span style="color:#333333"><strong>email</strong></span><span style="color:#bc6060">:1301334028</span>@<span style="color:#333333"><strong>qq</strong></span>.<span style="color:#333333"><strong>com</strong></span>
复制代码</span>

我们看到,执行结果和默认的结果是一致的,我们通过自定义序列化机制,修改了默认的序列化过程(让transient关键字失去了作用)。

注意:

细心的同学可能发现了我们在自定义序列化的过程中调用了defaultWriteObject()和defaultReadObject()方法。这两个方法是默认的序列化过程调用的方法。如果我们自定义序列化过程仅仅调用了这两个方法而没有任何额外的操作,这其实和默认的序列化过程没任何区别,大家可以试一下。

4、存在继承关系下的序列化

子类支持序列化,超类不支持序列化

我们先来了解这一种情况,如果子类支持序列化,父类不支持序列化,那么我们在序列化子类实例的时候必须显式的保存父类的状态。我们将前面的例子稍作修改:

<span style="color:#444444"><span style="color:#333333"><strong>class</strong></span> <span style="color:#880000"><strong>SuperClass</strong></span>{
    <span style="color:#333333"><strong>protected</strong></span> String name;
    <span style="color:#333333"><strong>protected</strong></span> <span style="color:#333333"><strong>int</strong></span> age;
    
    <span style="color:#333333"><strong>public</strong></span> String <span style="color:#880000"><strong>getName</strong></span>() {
    	<span style="color:#333333"><strong>return</strong></span> name;
    }
    
    <span style="color:#333333"><strong>public</strong></span> <span style="color:#333333"><strong>int</strong></span> <span style="color:#880000"><strong>getAge</strong></span>() {
    	<span style="color:#333333"><strong>return</strong></span> age;
    }
    
    <span style="color:#333333"><strong>public</strong></span> <span style="color:#880000"><strong>SuperClass</strong></span>(String name,<span style="color:#333333"><strong>int</strong></span> age) {
    	<span style="color:#333333"><strong>this</strong></span>.name=name;
    	<span style="color:#333333"><strong>this</strong></span>.age=age;
    }
    }
    
    <span style="color:#333333"><strong>class</strong></span> <span style="color:#880000"><strong>DeriveClass</strong></span> <span style="color:#333333"><strong>extends</strong></span> <span style="color:#880000"><strong>SuperClass</strong></span> <span style="color:#333333"><strong>implements</strong></span> <span style="color:#880000"><strong>Serializable</strong></span>{
    <span style="color:#333333"><strong>private</strong></span> String email;
    <span style="color:#333333"><strong>private</strong></span> String address;
    
    <span style="color:#333333"><strong>public</strong></span> <span style="color:#880000"><strong>DeriveClass</strong></span>(String name,<span style="color:#333333"><strong>int</strong></span> age,String email,String address) {
    	<span style="color:#333333"><strong>super</strong></span>(name,age);
    	<span style="color:#333333"><strong>this</strong></span>.email=email;
    	<span style="color:#333333"><strong>this</strong></span>.address=address;
    }
    
    <span style="color:#333333"><strong>public</strong></span> String <span style="color:#880000"><strong>getEmail</strong></span>() {
    	<span style="color:#333333"><strong>return</strong></span> email;
    }
    
    <span style="color:#333333"><strong>public</strong></span> String <span style="color:#880000"><strong>getAddress</strong></span>() {
    	<span style="color:#333333"><strong>return</strong></span> address;
    }
    
    <span style="color:#333333"><strong>private</strong></span> <span style="color:#333333"><strong>void</strong></span> <span style="color:#880000"><strong>writeObject</strong></span>(ObjectOutputStream out) <span style="color:#333333"><strong>throws</strong></span> IOException {  
        out.defaultWriteObject();  
        out.writeObject(name);
        out.writeInt(age);
    }  
    
    <span style="color:#333333"><strong>private</strong></span> <span style="color:#333333"><strong>void</strong></span> <span style="color:#880000"><strong>readObject</strong></span>(ObjectInputStream in) <span style="color:#333333"><strong>throws</strong></span> IOException, ClassNotFoundException {  
        in.defaultReadObject();  
        name=(String)in.readObject();
        age=in.readInt();
    }   
    
    <span style="color:#1f7199">@Override</span>
    <span style="color:#333333"><strong>public</strong></span> String <span style="color:#880000"><strong>toString</strong></span>() {
    	<span style="color:#333333"><strong>return</strong></span> <span style="color:#880000">"name:"</span>+getName()+<span style="color:#880000">"\nage:"</span>+getAge()+<span style="color:#880000">"\nemail:"</span>+getEmail()+<span style="color:#880000">"\naddress"</span>+getAddress();
    }
}
复制代码</span>

main方法我们修改为序列化子类对象即可:

<span style="color:#444444">DeriveClass superClass=<span style="color:#397300">new</span> DeriveClass(<span style="color:#880000">"gong"</span>,<span style="color:#880000">27</span>,<span style="color:#880000">"1301334028@qq.com"</span>,<span style="color:#880000">"NJ"</span>);
DeriveClass getObject=(DeriveClass) objectInputStream.readObject();
System.out.<span style="color:#397300">println</span>(<span style="color:#880000">"反序列化对象数据:"</span>);
System.out.<span style="color:#397300">println</span>(getObject);
复制代码</span>

运行代码发现报错了,报错如下:

<span style="color:#444444">Exception <span style="color:#333333"><strong>in</strong></span> thread <span style="color:#880000">"main"</span> java.<span style="color:#397300">io</span>.InvalidClassException: com.learn.example.DeriveClass; no valid constructor
	at java.<span style="color:#397300">io</span>.ObjectStreamClass$ExceptionInfo.newInvalidClassException(Unknown Source)
	at java.<span style="color:#397300">io</span>.ObjectStreamClass.checkDeserialize(Unknown Source)
	at java.<span style="color:#397300">io</span>.ObjectInputStream.readOrdinaryObject(Unknown Source)
	at java.<span style="color:#397300">io</span>.ObjectInputStream.readObject0(Unknown Source)
	at java.<span style="color:#397300">io</span>.ObjectInputStream.readObject(Unknown Source)
	at com.learn.example.RunMain.main(RunMain.java:<span style="color:#880000">88</span>)
复制代码</span>

我们来仔细分析下,为什么会这样。DeriveClass支持序列化,其父类不支持序列化,所以这种情况下,子类在序列化的时候需要额外的序列化父类的域(如果有这个需要的话)。那么在反序列的时候,由于构建DeriveClass实例的时候需要先调用父类的构造函数,然后才是自己的构造函数。反序列化时,为了构造父对象,只能调用父类的无参构造函数作为默认的父对象,因此当我们取父对象的变量值时,它的值是调用父类无参构造函数后的值。如果你考虑到这种序列化的情况,在父类无参构造函数中对变量进行初始化。或者在readObject方法中进行赋值。

如果你现在在JAVA这条路上挣扎,也想在IT行业拿高薪,可以参加我们的训练营课程,选择最适合自己的课程学习,技术大牛亲授,7个月后,进入名企拿高薪。我们的课程内容有:Java工程化、高性能及分布式、高性能、深入浅出。高架构。性能调优、Spring,MyBatis,Netty源码分析和大数据等多个知识点。如果你想拿高薪的,想学习的,想就业前景好的,想跟别人竞争能取得优势的,想进阿里面试但担心面试不过的,你都可以来,q群号为:779792048

注:加群要求

1、具有1-5工作经验的,面对目前流行的技术不知从何下手,需要突破技术瓶颈的可以加。

2、在公司待久了,过得很安逸,但跳槽时面试碰壁。需要在短时间内进修、跳槽拿高薪的可以加。

3、如果没有工作经验,但基础非常扎实,对java工作机制,常用设计思想,常用java开发框架掌握熟练的,可以加。

4、觉得自己很牛B,一般需求都能搞定。但是所学的知识点没有系统化,很难在技术领域继续突破的可以加。

5.阿里Java高级大牛直播讲解知识点,分享知识,多年工作经验的梳理和总结,带着大家全面、科学地建立自己的技术体系和技术认知!

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值