JDK1.8 java.io.Serializable接口详解
java.io.Serializable接口是一个标志性接口,在接口内部没有定义任何属性与方法。只是用于标识此接口的实现类可以被序列化与反序列化。但是它的奥秘并非像它表现的这样简单。现在从以下几个问题入手来考虑。
- 希望对象的某些属性不参与序列化应该怎么处理?
- 对象序列化之后,如果类的属性发生了增减那么反序列化时会有什么影响呢?
- 如果父类没有实现java.io.Serializable接口,子类实现了此接口,那么父类中的属性能被序列化吗?
- serialVersionUID属性是做什么用的?必须申明此属性吗?如果不申明此属性会有什么影响?如果此属性的值发生了变化会有什么影响?
- 能干预对象的序列化与反序列化过程吗?
在解决这些问题之前,先来看一看如何进行对象的序列化与反序列化。定义一个Animal类,并实现java.io.Serializable接口。如下代码所示把Animal实例序列化为文件保存在硬盘中。
class Animal implements Serializable{
/**
*
*/
private static final long serialVersionUID = 8822818790694831649L;
private String name;
private String color;
private String[] alias;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getColor() {
return color;
}
public void setColor(String color) {
this.color = color;
}
public String[] getAlias() {
return alias;
}
public void setAlias(String[] alias) {
this.alias = alias;
}
}
对Animal对象进行序列化与反序列化的代码如下所示:
// 反序列化
static void unserializable() throws FileNotFoundException, IOException, ClassNotFoundException{
ObjectInputStream ois = null;
try {
ois = new ObjectInputStream(new FileInputStream("D://animal.dat"));
Animal animal = (Animal) ois.readObject();
System.out.println(animal);
} finally {
if( null != ois ){
ois.close();
}
} }
// 序列化
static void serializable() throws FileNotFoundException, IOException{
ObjectOutputStream oos = null;
try {
oos = new ObjectOutputStream(new FileOutputStream("D://animal.dat"));
Animal animal = new Animal();
animal.setName("Dog");
animal.setColor("Black");
animal.setAlias(new String[]{"xiaoHei", "Gou", "GuaiGuai"});
oos.writeObject(animal);
oos.flush();
} finally {
if(null != oos){
oos.close();
}
}
}
现在利用以上序列化与反序列化Animal对象的例子来逐步回答本文开始时提出的几个问题。
一、如何让某些属性不参与序列化与反序列化的过程?
假定在Animal对象中,我们希望alias属性不能被序列化。这个问题非常容易解决,只需要使用transient关键定修饰此属性就可以了。对Animal类的简单修改如下所示:
class Animal implements Serializable{
/**
*
*/
private static final long serialVersionUID = 8822818790694831649L;
private String name;
private String color;
private transient String[] alias;
如果一个属性被transient关键字修饰,那么此属性就不会参与对象序列化与反序列化的过程。
二、类的属性发生了增减那么反序列化时会有什么影响?
假定在设计Animal类的时候由于考虑不周全而需要添加age属性,那么如果在添加此之前Animal对象已序列化为animal.dat文件,那么在添加age属性之后,还能不能成功的反序列化呢?新的Animal类的片段如下所示:
class Animal implements Serializable{
/**
*
*/
private static final long serialVersionUID = 8822818790694831649L;
private String name;
private String color;
private transient String[] alias;
private int age;
再次调用反序列化的方法,使用添加age属性之前的animal.dat文件进行反序列化,运行结果表明还是能正常的反序列化,只是新添加的属性为默认值。
反过来考虑,如果把animal.dat文件中存在的name属性删除,那么还能使用animal.dat文件进行反序列化吗?修改之后的Animal类如下所示:
class Animal implements Serializable{
/**
*
*/
private static final long serialVersionUID = 8822818790694831649L;
// private String name;
private String color;
private transient String[] alias;
private int age;
调用反序列化的方法,使用删除name属性之前的animal.dat文件进行反序列化,运行结果表时还是能正常的反序列化。由此可知,类的属性的增删并不能对对象的反序列化造成影响。
三、继承关系在序列化过程中的影响?
假定有父类Living没有实现java.io.Serializable接口,子类Human实现了java.io.Serializable接口,那么在序列化子类时父类中的属性能被序列化吗?先给出Living与Human类的定义如下所示:
class Living{
private String environment; public String getEnvironment() {
return environment;
} public void setEnvironment(String environment) {
this.environment = environment;
}
} class Human extends Living implements Serializable{ /**
*
*/
private static final long serialVersionUID = -4389621464687273122L; private String name;
private double weight;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public double getWeight() {
return weight;
}
public void setWeight(double weight) {
this.weight = weight;
}
@Override
public String toString() {
return getEnvironment() + " : " + name + ", " + weight;
}
}
通过代码序列化Human对象得到human.dat文件,再从此文件中进行反序列化得出结果为:
null : Wg, 130.0
也可以使用文件编辑工具Notepad++,查看human.dat文件如下所示:
在这个文件中看不到任何与父类中的environment属性相同的内容,说明这个属性并没有被序列化。
修改父类Living,使之实现java.io.Serialazable接口,父类修改之后代码片段如下所示:
class Living implements Serializable{
序列化Human对象再次得到human.dat文件,再从此文件中反序列化得出结果为:
human environment : Wg, 130.0
再次通过Notepad++,查看human.dat文件如下所示:
从这个文件中也可以清楚的看到父类Living中的environment属性被成功的序列化。
由此可得出结论在继承关系中如果父类没有实现java.io.Serializable接口,那么在序列化子类时即使子类实现了java.io.Serializable接口也不能把父类中的属性序列化。
四、serialVersionUID属性
在使用Eclipse之类的IDE开发工具时,如果类实现了java.io.Serializable接口,那么IDE会警告让生成如下属性:
private static final long serialVersionUID = 8822818790694831649L;
这个属性必须被申明为static的,最好是final不可修改的。此属性被用于序列化与反序列化过程中的类信息校验,如果此属性的值在序列化之后发生了变化,那么可序列化的文件就不能再反序列化,会抛出InvalidClassException异常。如下所示,在序列化之生修改此属性,运行代码的结果:
// 序列化之生手动修改了serialVersionUID属性
private static final long serialVersionUID = 1822818790694831649L;
// private static final long serialVersionUID = 8822818790694831649L;
这时反序列化会出现如下的异常信息:
java.io.InvalidClassException: j2se.Animal; local class incompatible: stream classdesc serialVersionUID = 8822818790694831649, local class serialVersionUID = 1822818790694831649
at java.io.ObjectStreamClass.initNonProxy(ObjectStreamClass.java:621)
at java.io.ObjectInputStream.readNonProxyDesc(ObjectInputStream.java:1623)
at java.io.ObjectInputStream.readClassDesc(ObjectInputStream.java:1518)
at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:1774)
at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1351)
at java.io.ObjectInputStream.readObject(ObjectInputStream.java:371)
at j2se.SerializableTest.unserializable(SerializableTest.java:58)
at j2se.SerializableTest.animalUnSerializable(SerializableTest.java:50)
at j2se.SerializableTest.main(SerializableTest.java:26)
由此可见,如果序列化之后修改了serialVersionUID属性,那么序列化的文件就不能再成功的反序列化。
当然,在工作中也见过很多程序员并不在意IDE警告,不会为类申明serialVersionUID属性,因为这个属性也不是必须的。通过把类的serialVersionUID属性删除也可以成功的序列化与反序列化,如果类没有显式的申明serialVersionUID属性,那么JVM会依据类的各方面信息自动生成serialVersionUID属性值,但是由于不同的JVM生成serialVersionUID的原理存在差异。所以强烈建议程序员显式申明serialVersionUID属性,并强烈建议使用private static final修饰此属性。
五、如果干预对象的序列化与反序列化过程?
在上面例子中的Animal类中定义了一个由transient关键字修饰的alias变量,由于被transient修饰所以它不会被序列化。但是希望在序列化的过程中把alias数组的各个元素序列化,并在反序列化过程把数组中的元素还原到alias数组中。java.io.Serializable接口虽然没有定义任何方法,但是可以通过在要序列化的类中的申明如下准确签名的方法:
/**
* 序列化对象时调用此方法完成序列化过程
* @param o
* @throws IOException
*/
private void writeObject(ObjectOutputStream o) throws IOException{ }
/**
* 反序列化对象时调用此方法完成反序列化过程
* @param o
* @throws IOException
* @throws ClassNotFoundException
*/
private void readObject(ObjectInputStream o) throws IOException, ClassNotFoundException{ }
/**
* 反序列化的过程中如果没有数据时调用此方法
* @throws ObjectStreamException
*/
private void readObjectNoData() throws ObjectStreamException{ }
在Animal类中可以申明以上的方法,如下所示:
class Animal implements Serializable{
/**
*
*/
private static final long serialVersionUID = 8822818790694831649L;
private String name;
private String color;
private transient String[] alias;
private int age; /**
* 序列化对象时调用此方法完成序列化过程
* @param o
* @throws IOException
*/
private void writeObject(ObjectOutputStream o) throws IOException{
o.defaultWriteObject(); // 默认写入对象的信息
o.writeInt(alias.length);// 写入alias元素的个数
for(int i=0;i<alias.length;i++){
o.writeObject(alias[i]);// 写入alias数组中的每一个元素
}
}
/**
* 反序列化对象时调用此方法完成反序列化过程
* @param o
* @throws IOException
* @throws ClassNotFoundException
*/
private void readObject(ObjectInputStream o) throws IOException, ClassNotFoundException{
// 读取顺序与写入顺序一致
o.defaultReadObject(); // 默认读取对象的信息
int length = o.readInt(); // 读取alias元素的个数
alias = new String[length];
for(int i=0;i<length;i++){
alias[i] = o.readObject().toString(); // 读取元素存入数组
}
}
到目前为止,我们已可以自定义对象的序列化与反序列化的过程。比如通过以下程序序列化对象,得到animal.dat文件。
static void animalSerializable(){
Animal animal = new Animal();
animal.setName("Dog");
animal.setColor("Black");
animal.setAge(100);
animal.setAlias(new String[]{"xiaoHei", "Gou", "GuaiGuai"});
serializable(animal, "D://animal.dat");
}
通过Notepad++打开animal.dat文件如下图所示:
可以从上图中发现,实际上可以序列化的文件中找到部分对象信息。现在我们希望能把信息加密之后再序列化,并在反序列化时自动解密。在java.io.Serializable接口的实现类中还可以定义如下的方法,用于替换序列化过程中的对象与解析反序列化过程中的对象。
/**
* 在writeObject方法之前调用,通过此方法替换序列化过程中需要替换的内部。
* @return
* @throws ObjectStreamException
*/
Object writeReplace() throws ObjectStreamException{ } /**
* 在readObject方法之前调用,用于把writeReplace方法中替换的对象还原
* @return
* @throws ObjectStreamException
*/
Object readResolve() throws ObjectStreamException{ }
在Animal对象的序列化与反序列化的过程中可以利用以上的两个方法进行加密与解密,如下所示:
/**
* 在writeObject方法之前调用,通过此方法替换序列化过程中需要替换的内部。
* @return
* @throws ObjectStreamException
*/
Object writeReplace() throws ObjectStreamException{
try {
Animal animal = new Animal();
String key = String.valueOf(serialVersionUID); // 简单使用erialVersionUID做为对称算法的密钥
animal.setAge(getAge() << 2); // 对于整数就简单的处理为向左移动两位
animal.setName(DesUtil.encrypt(getName(), key)); // 加密
animal.setColor(DesUtil.encrypt(getColor(), key));
String[] as = new String[getAlias().length];
for(int i=0;i<as.length;i++){
as[i] = DesUtil.encrypt(getAlias()[i], key);
}
animal.setAlias(as);
return animal;
} catch (Exception e) {
throw new InvalidObjectException(e.getMessage());
}
} /**
* 在readObject方法之前调用,用于把writeReplace方法中替换的对象还原
* @return
* @throws ObjectStreamException
*/
Object readResolve() throws ObjectStreamException{
try {
Animal animal = new Animal();
String key = String.valueOf(serialVersionUID);
animal.setAge(getAge() >> 2);
animal.setName(DesUtil.decrypt(getName(), key)); // 解密
animal.setColor(DesUtil.decrypt(getColor(), key));
String[] as = new String[getAlias().length];
for(int i=0;i<as.length;i++){
as[i] = DesUtil.decrypt(getAlias()[i], key);
}
animal.setAlias(as);
return animal;
} catch (Exception e) {
throw new InvalidObjectException(e.getMessage());
}
}
再次使用Notepad++打开animal.dat文件如下图所示,在其中就不会再存在Animal对象的信息。
所以综上所述,对象的序列化与反序列化过程是完全可控的,利用writeReplace与writeObject方法控制序列化过程,readResolve与readObject方法控制反序列化过程。在序列化过程中与反序列化过程中方法的调用顺序如下所示:
序列化过程:writeReplace –> writeObject
反序列化过程:readObject –> readResolve
JDK1.8 java.io.Serializable接口详解的更多相关文章
- java.io.DataInput接口和java.io.DataOutput接口详解
public interface DataInput DataInput 接口用于从二进制流中读取字节,并重构所有 Java 基本类型数据.同时还提供根据 UTF-8 修改版格式的数据重构 Strin ...
- java.io.ObjectInputStream类详解
1.public class ObjectInputStream extends InputStream implements ObjectInput, ObjectStreamConstants分析 ...
- JDK源码阅读(五)java.io.Serializable接口
package java.io; public interface Serializable { } (1)实现Serializable接口的类,将会被提示提供一个 serialVersionUID ...
- Java IO 输入输出流 详解 (一)***
首先看个图: 这是Javaio 比较基本的一些处理流,除此之外我们还会提到一些比较深入的基于io的处理类,比如console类,SteamTokenzier,Externalizable接口,Seri ...
- java 锁 Lock接口详解
一:java.util.concurrent.locks包下常用的类与接口(lock是jdk 1.5后新增的) (1)Lock和ReadWriteLock是两大锁的根接口,Lock代表实现类是Reen ...
- java抽象类和接口详解
接口和内部类为我们提供了一种将接口与实现分离的更加结构化的方法. 抽象类与接口是java语言中对抽象概念进行定义的两种机制,正是由于他们的存在才赋予java强大的面向对象的能力.他们两者之间对抽象概念 ...
- Java中的接口详解
接口 是Java语言中一种引用类型,是方法的集合,如果说类的内部封装了成员变量.构造方法和成员方法,那么接口的内部主要就是封装了方法,包含抽象方法(JDK 7及以前),默认方法和静态方法(JDK 8) ...
- java抽象类与接口 详解
在面向对象的概念中,我们知道所有的对象都是通过类来描绘的,但是并不是所有的类都是用来描绘对象的,如果一个类中没有包含足够的信息来描绘一个具体的对象,这样的类就是抽象类. 抽象类往往用来表征我们在对问题 ...
- java.lang.Comparable 接口 详解
参考https://blog.csdn.net/itm_hadf/article/details/7432782 http://www.blogjava.net/jjshcc/archive/2011 ...
随机推荐
- java + selenium + testng实现简单的UI自动化
新建Maven项目,添加需要的依赖 1.新建一个Maven项目 2.在pom.xml中添加需要的依赖,这里只要selenium和testng就行 <!-- https://mvnreposito ...
- HMAC哈希消息认证码
收藏 137 14 hmac 编辑 HMAC是密钥相关的哈希运算消息认证码,HMAC运算利用哈希算法,以一个密钥和一个消息为输入,生成一个消息摘要作为输出. 中文名 哈希消息认证码 外文名 H ...
- LeetCode 1046. 最后一块石头的重量(1046. Last Stone Weight) 50
1046. 最后一块石头的重量 1046. Last Stone Weight 题目描述 每日一算法2019/6/22Day 50LeetCode1046. Last Stone Weight Jav ...
- Delphi 开发微信公众平台 (三)- 获取微信服务器IP地址
如果公众号基于安全等考虑,需要获知微信服务器的IP地址列表,以便进行相关限制,可以通过该接口获得微信服务器IP地址列表或者IP网段信息. 接口调用请求说明 http 请求方式: GET https:/ ...
- php mysqli 预处理操作数据库
用到的SQL表 CREATE TABLE `student_01` ( `id` int(11) NOT NULL AUTO_INCREMENT, `name` varchar(255) CHARAC ...
- Maven 初学+http://mvnrepository.com/
了解 maven是一款服务于java平台的自动化构建工具(项目管理工具) 构建:全方位.多角度.深层次地建立 项目构建是一个项目从:源代码.编译.测试.打包.部署.运行的过程 用来解决团队开发遇到的问 ...
- IE浏览器 location.href 不跳转
var url = "https://www.cnblogs.com/zing"; location.href = url; window.event.returnValue = ...
- python-tyoira基本
目录 .Typora安装 我们在之前的时候记录笔记就是使用word和记事本,但是从今天开始我们要更换软件,记录笔记使用Typora软件,为什么要使用Typora的软件呢,是因为我们程序员不只是写代码这 ...
- C# NPOI 导入与导出Excel文档 兼容xlsx, xls(xf13中已经引用了xlsx的npoi)
这里使用的NPOI版本为: 2.1.3.1 官方下载地址: http://npoi.codeplex.com/releases 版本内包含.Net 2.0 与.Net 4.0 .Net 4.0中包含文 ...
- Java项目部分总结
一.数据库sql操作: 1.三表查询的时候,最后的条件由于当前字段必须判断是属于哪个表,所以需要注明根据哪个表中的字段进行判断: 并且再在后面加上limit的时候,需要注意先进行添加,避免系统不能识别 ...