上篇文章《Java对象的序列化与反序列化》简单介绍了Java中对象序列化和反序列化的一些基础知识。看完那篇文章,有朋友留言说:终于明白Java的序列化了。我只想说:小伙子,你真是图森破画。通过那篇文章,读者可以了解如何序列化和反序列化Java对象。但是,还有一些理论知识没有深入讲解。本文在上一篇文章的基础上,深入挖掘底层原理,重点解决以下问题:Java序列化是如何实现的?为什么java.io.Serializable接口可以序列化?瞬态的作用是什么?如何打破序列化中的瞬态限制?如何自定义序列化策略?自定义序列化策略是如何调用的?ArrayList实现序列化有什么好处?序列化基础知识关于序列化的用法和基础知识,由于不是本文的重点,这里就不详细介绍了。具体请参考Java对象的序列化与反序列化。这里只是一个简短的回顾。1、在Java中,只要一个类实现了java.io.Serializable接口,就可以被序列化。2.通过ObjectOutputStream和ObjectInputStream对对象进行序列化和反序列化。3、虚拟机是否允许反序列化,不仅仅取决于类路径和函数代码是否一致,还有很重要的一点就是两个类的序列化ID是否一致(即privatestaticfinallongserialVersionUID)4.序列化不保存静态变量。5.如果要序列化父类对象,需要让父类也实现Serializable接口。6、transient关键字的作用是控制变量的序列化。在变量声明前加上这个关键字可以防止变量被序列化到文件中。transient变量被反序列化后的值被设置为初始值,比如int类型为0,object类型为null。7.服务器向客户端发送序列化的对象数据。对象中有些数据比较敏感,比如密码字符串等,希望在序列化的时候对密码字段进行加密,如果客户端有解密密钥,只有在客户端反序列化的时候,才能读取到密码,这样可以在一定程度上保证序列化对象的数据安全。ArrayList的序列化为了深入介绍序列化,本文将从Java源码中的ArrayList类入手。查看Java本身如何使用序列化。在介绍ArrayList序列化之前,我们先来思考一个问题:“Q:如何自定义序列化和反序列化策略?”带着这个疑问,我们来看java.util.ArrayList1publicclassArrayListextendsAbstractListimplementsList,RandomAccess,Cloneable,java.io.Serializable{privatestaticfinallongserialVersionUID=8683452581122892189L;transientObject[]elementData;//的源码non-privatetosimplifynestedclassaccessprivateintsize;}作者省略了其他成员变量,从上面的代码我们可以知道ArrayList实现了java.io.Serializable接口,那么我们可以对其进行序列化和反序列化。因为负责保存元素的elementData是transient的,我们认为这个成员变量的内容不会被序列化保留。我们写了一个Demo,验证了下我们的想法:code2publicstaticvoidmain(String[]args)throwsIOException,ClassNotFoundException{ListstringList=newArrayList();stringList.add("hello");stringList.add("world");stringList.add("hollis");stringList.add("chuang");System.out.println("initStringList"+stringList);ObjectOutputStreamobjectOutputStream=newObjectOutputStream(newFileOutputStream("stringlist"));objectOutputStream.writeObject(stringList);IOUtils.close(objectOutputStream);Filefile=newFile("stringlist");ObjectInputStreamobjectInputStream=newObjectInputStream(newFileInputStream(file));ListnewStringList=(List)objectInputStream.readObject();IOUtils。close(objectInputStream);if(file.exists()){file.delete();}System.out.println("newStringList"+newStringList);}//initStringList[hello,world,hollis,chuang]//newStringList[hello,world,hollis,chuang]了解了ArrayList中大家都知道ArrayList底层是通过数组来实现的,所以数组elementData其实就是用来保存列表中的元素的。通过这个属性的声明方式,我们认为应该是不能通过序列化来持久化的。“Q:为什么代码2的结果通过序列化和反序列化保留了List中的元素?”writeObject和readObject方法定义了ArrayList中的一个方法:writeObject和readObject。这里先给出结论:在序列化过程中,如果在序列化类中定义了writeObject和readObject方法,虚拟机会尝试调用对象类中的writeObject和readObject方法进行用户自定义的序列化和反序列化。如果没有这个方法,默认调用ObjectOutputStream的defaultWriteObject方法和ObjectInputStream的defaultReadObject方法。也就是说,用户自定义的writeObject和readObject方法可以让用户控制序列化的过程,例如序列化后的值可以在序列化过程中动态改变。我们发现ArrayList中有这两个方法的实现,所以基本可以肯定elementData可以序列化持久化,这肯定和这两个方法有关。虽然声明为transient,但是我们来看看ArrayList类中的这两个方法该方法的具体实现:代码3privatevoidreadObject(java.io.ObjectInputStreams)throwsjava.io.IOException,ClassNotFoundException{elementData=EMPTY_ELEMENTDATA;//Readinsize,andanyhiddenstuffs.defaultReadObject();//Readincapacitys.readInt();//ignoredif(size>0){//belikeclone(),allocatearraybaseduponsizenotcapacityensureCapacityInternal(size);Object[]a=elementData;//Readinalelementsintheproperorder.for(inti=0;iwriteObject0--->writeOrdinaryObject--->writeSerialData--->invokeWriteObject下面看看invokeWriteObject:voidinvokeWriteObject(Objectobj,ObjectOutputStreamout)throwsIOException,UnsupportedOperationException{if(writeObjectMethod!=null){try{writeObjectMethod.invoke(obj,newObject[]{out});}catch(InvocationTargetExceptionex){Throwableth=ex.getTargetException();if(thinstanceofIOException){throw(IOException)th;}else{throwMiscException(th);}}catch(IllegalAccessExceptionex){//不应该发生,asaccesscheckshavebeensuppressedthrownewInternalError(ex);}}else{thrownewUnsupportedOperationException();}}wherewriteObjectMethod.invoke();是关键,通过反射调用writeObjectMethod方法。这个writeObjectMethod官方的解释是这样的:class-definedwriteObjectmethod,ornullifnone在我们的例子中,这个方法就是我们在ArrayList中定义的writeObject方法。通过反射调用。至此,我们试着回答刚才提出的问题:“问:如果一个类包含writeObject和readObject方法,这两个方法是如何调用的?答:在使用ObjectOutputStream的writeObject方法和ObjectInputStream的readObject方法时,会被反射调用。”至此,我们介绍了ArrayList的序列化方法。那么,不知道有没有人提出过这样的问题:“问:Serializable明明是一个空接口,它如何保证只有实现了这个接口的方法才能被序列化和反序列化?”Serializable接口的定义:publicinterfaceSerializable{}如果您尝试序列化一个没有实现Serializable接口的类,将抛出java.io.NotSerializableException。为什么是这样?Serializable只是一个空接口,如何实现呢?其实这个问题很好回答,我们回到刚才ObjectOutputStream的writeObject的调用栈:writeObject--->writeObject0--->writeOrdinaryObject--->writeSerialData--->invokeWriteObjectwriteObject0有这么一块方法中的代码:if(objinstanceofString){writeString((String)obj,unshared);}elseif(cl.isArray()){writeArray(obj,desc,unshared);}elseif(objinstanceofEnum){writeEnum((Enum>)obj,desc,unshared);}elseif(objinstanceofSerializable){writeOrdinaryObject(obj,desc,unshared);}else{if(extendedDebugInfo){thrownewNotSerializableException(cl.getName()+"\n"+debugInfoStack.toString());}else{thrownewNotSerializableException(cl.getName());}}序列化操作时会判断要序列化的类是否为Enum,Array,Serializable类型,如果不是则直接抛出NotSerializableException.》问:Serializable明明是一个空接口,它如何保证只有实现了这个接口的方法才能被序列化和反序列化?答:类在序列化的过程中,会通过instanceof关键字来判断一个类是否继承Serializable类,如果没有,直接抛出NotSerializableException。”总结1.一个类如果想被序列化,需要实现Serializable接口。否则会抛出NotSerializableException,因为在序列化操作时会检查类型,序列化的类必须属于Enum、Array和Serializable类型中的任意一种。2.在变量声明前加上this关键字可以防止变量被序列化到文件中。3.在类中添加writeObject和readObject方法可以实现自定义的序列化策略。【本文为专栏作家霍利斯原创文章,作者微信公众号Hollis(ID:hollishuang)】点此阅读更多本作者好文