java.io.Serializable接口是一个标志性接口,在接口内部没有定义任何属性与方法。只是用于标识此接口的实现类能够被序列化与反序列化。可是它的奥秘并不是像它表现的这样简单。如今从如下几个问题入手来考虑。java
在解决这些问题以前,先来看一看如何进行对象的序列化与反序列化。定义一个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对象的例子来逐步回答本文开始时提出的几个问题。数组
1、如何让某些属性不参与序列化与反序列化的过程?ide
假定在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关键字修饰,那么此属性就不会参与对象序列化与反序列化的过程。性能
2、类的属性发生了增减那么反序列化时会有什么影响?开发工具
假定在设计Animal类的时候因为考虑不周全而须要添加age属性,那么若是在添加此以前Animal对象已序列化为animal.dat文件,那么在添加age属性以后,还能不能成功的反序列化呢?新的Animal类的片断以下所示:this
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文件进行反序列化,运行结果表时仍是能正常的反序列化。由此可知,类的属性的增删并不能对对象的反序列化形成影响。
3、继承关系在序列化过程当中的影响?
假定有父类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接口也不能把父类中的属性序列化。
4、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修饰此属性。
5、若是干预对象的序列化与反序列化过程?
在上面例子中的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