很多时候我们需要将一个类的实例转换为二进制数据存储或者通过网络发送。这个过程称为序列化。如果将二进制数据解析成位于内存中的类实例或相关数据结构,则称为反序列化。所有的序列化算法都遵循一定的套路,例如:classA{publicinta=1;publicintb=2;protectedBb=newB();privatefloatc=3.0;}如果要序列化classA的一个实例,通常需要设置变量a,写入b的值对应的二进制数,然后得到B类实例序列化后的二进制数据,最后写入变量c的值的二进制数据。到这里,可以体会到序列化其实是有递归的性质的。在序列化过程中如果遇到基本类型,可以直接获取其对应的二进制数据。如果遇到类实例,需要先将其序列化,获取对应的二进制数据。在序列化过程中,需要了解对应类的定义,但是如果我们不知道要序列化的对象,比如我们看不到类A的定义,只能得到一个实例对象对应A,那么它的这个时候怎么排序。这就需要利用java语言的反射特性。java编译器在编译A类时,不仅会为其每个字段分配内存,还会设置并存储A类的相关信息,比如A中有多少个字段,字段的类型是int,float,stirng,或者特定的类对象。这些信息被设置并存储在一起。我们只要使用java反射提供的API,就可以获得这些信息,从而可以使用任何类实现序列化。因此,序列化的通用套路是:1.获取要序列化的类实例;2、获取类中各个字段的属性、类型等相关信息。3.如果字段属于基础数据,则获取其值的二进制数据。4.如果对应的字段是类实例,则先递归序列化该实例。按照上面的步骤,当我们需要序列化任意一个类实例时,首先通过getClass获取对应的Class类实例,然后调用getDeclaredFields()接口获取该实例的所有字段,包括public、protected、private,或者调用getFields()获取类实例声明或继承的公共字段。在序列化的时候,我们不能忘记对当前类实例的父类进行序列化,所以我们可以调用getSuperClass()来获取当前实例的父类,这个过程会一直持续到到达根类为止。每个字段对应一个名为Field的元类,可以通过该类的相关接口获取字段的值。获取字段的数据,首先需要判断字段的类型。如果是布尔类型,可以调用Field类的getBoolean接口获取数据。如果是int类型,可以通过getInteger()接口获取数据。如果该字段是一个类对象,那么它必须递归地获取它的二进制数据。如果该字段是基本类型,那么可以通过调用它的getString()来获取其值的字符串形式。在获取字段类型之前,我们还需要知道该字段的修饰属性,比如是public还是private,是否是static等,这些属性是通过Field类的接口getModifier()获取的。调用它会返回一个整数值,这与Bits设置为1或0来表示修饰符属性有关。java语法中修饰属性有11种,所以有11位对应,但是我们不需要去分析哪一位被设置为1来获取字段属性。Java反射提供了一个具体的类Modifier,getModifier返回的值可以进入Modifier类的isPublic、isPrivate等接口查询字段对应的修改属性。在序列化的时候,我们不得不忽略静态属性的字段,所以它们是硬编码的,所以字段的静态属性可以通过Modifier.isStatic(field.getModifier())得到的结果来判断。众所周知,对于protected或者private字段,外部是不能直接读取的,但是序列化必须能够读取到这类字段的值,否则无法进行序列化。Field类提供了setAccessible(true)来克服这个限制。另一个要考虑的因素是该字段是否为数组类型。Java反射提供了元类Array来处理它。假设实例对象obj是一个数组,那么Array.getLength(obj)可以得到数组的长度,Array.get(obj,i)可以得到第i个元素对象。最后,我们需要考虑序列化文件的格式。我们使用xml格式来存储序列化的结果。比如上面的例子,字段a序列化后对应的是“\1\”。具体情况,我们会在后续代码中慢慢观察。首先,我们使用IntelliJ创建一个maven项目。由于我们需要将数据序列化成XML文件,所以需要用到JDOM接口,所以在pom.xml中添加如下依赖:
