如何使用反射递归序列化对象?
Posted
技术标签:
【中文标题】如何使用反射递归序列化对象?【英文标题】:How to recursively serialize an object using reflection? 【发布时间】:2011-02-07 01:29:32 【问题描述】:我想导航到对象的第 N 级,并以字符串格式序列化它的属性。 例如:
class Animal
public String name;
public int weight;
public Animal friend;
public Set<Animal> children = new HashSet<Animal>() ;
应该这样序列化:
name:"Monkey",
weight:200,
friend:name:"Monkey Friend",weight:300 ,children:...if has children,
children:name:"MonkeyChild1",weight:100,children:... recursively nested
您可能会注意到它类似于将对象序列化为 json。我知道有很多库(Gson,Jackson...)可以做到这一点,你能给我一些关于如何自己写这个的指导性想法吗?
【问题讨论】:
这是否有特定的部分被证明是困难的? 如果你知道那里有很多库,为什么不直接使用一个? 您能告诉我们到目前为止您尝试了什么,以便我们有一个起点吗? @Justin Niessner :我不知道如何检查应该递归序列化的字段。 这只是简单的递归:内省一种类型(具有超类型等)的字段、方法(参见 JDK javadocs for Class);使用 java.lang.reflect.Field 查找字段的实际值,冲洗重复。如果你想变得花哨,跟踪已经解析的对象来处理循环(或共享引用)。 【参考方案1】:序列化基本上是深度克隆。
您需要跟踪每个对象引用是否重复出现(例如,使用IdentityHashMap)。你的最终实现方法是什么(如果不是外部库)记得检查对象重复出现,否则你可能会陷入无限循环(当对象 A 引用对象 B 时又引用对象 A 或更复杂的循环对象图)。
一种方法是使用类似 DFS 的算法遍历对象图并从那里构建克隆(序列化字符串)。
这个伪代码有望解释如何:
visited =
function visit(node)
if node in visited
doStuffOnReoccurence(node)
return
visited.add(node)
doStuffBeforeOthers(node)
for each otherNode in node.expand() visit(otherNode)
doStuffAfterOthers(node)
示例中的访问集是我将使用身份集(如果有的话)或 IdentityHashMap 的地方。
当反射性地查找字段时(即 node.expand() 部分),请记住也要遍历超类字段。
反射不应该用在“正常”的开发案例中。反射将代码作为数据处理,您可以忽略所有正常的对象访问限制。我仅将这种反射式深拷贝用于测试:
在检查不同类型对象的深度对象图相等性的测试中
在分析对象图大小和其他属性的测试中
【讨论】:
是的,你提到了IdentityHashMap,我注意到Google Gson 可以在一行中完成此特定任务:
String json = new Gson().toJson(animal);
顺便说一句,反过来也一样简单:
Animal animal = new Gson().fromJson(json, Animal.class);
我还没有看到另一个对泛型、集合/映射和(嵌套)javabean 提供更好支持的 JSON 序列化程序。
更新: 言归正传,您只需要了解反射 API。我建议先让自己通过Sun tutorial on the subject。简而言之,您可以使用Object#getClass()
和java.lang.Class
、java.lang.reflect.Method
等提供的所有方法来确定一个和另一个。 Google Gson 是开源的,您也可以从中受益。
【讨论】:
谢谢,我经常使用 Gson 和 jackson、jsonlib。我想知道如何使用反射来编写它。 嗯?为什么要重新发明***?无论如何:Gson 是开源的。受益匪浅。 lol,json之父说,reinvent Wheels的好处是,你可以找到一个round one。是的,使用 Gson 非常简单,但是我仍然不知道如何自己编写,如果有一天我遇到类似的问题但没有可以使用的库怎么办? 我不确定这是否有意义,但没关系。 :) 但即便如此,也许看看其他库是如何做到的?还有更简单的包;例如,Hessian 具有相当简单的基于反射的序列化器/反序列化器。 JDK 有 bean 序列化器(忘记了它的名字),等等。去看看那些库是怎么做的。但这一切都归结为使用 java.lang.Class 中的方法 - getDeclaredFields / Methods 等(并为超类型递归地执行此操作)。然后动态访问字段/方法。没有什么特别花哨的。 在问这个问题之前,我已经检查了 Gson 源。 Gson 是一个成熟的序列化器,可以处理大多数情况。它的源代码有点过度封装,我认为对于简单的问题,这可以在 100 行代码内完成。【参考方案3】:解决此问题的一种简洁方法是使用 visitor 模式来将编码实现与业务对象分开。有些人会争辩说,您可以简单地实现Externalizable
接口以及readExternal
/ writeExternal
但这有以下问题:
示例
/**
* Our visitor definition. Includes a visit method for each
* object it is capable of encoding.
*/
public interface Encoder
void visitAnimal(Animal a);
void visitVegetable(Vegetable v);
void visitMineral(Mineral m);
/**
* Interface to be implemented by each class that can be encoded.
*/
public interface Encodable
void applyEncoder(Encoder e);
public class Animal implements Encodable
public void applyEncoder(Encoder e)
// Make call back to encoder to encode this particular Animal.
// Different encoder implementations can be passed to an Animal
// *without* it caring.
e.visitAnimal(this);
通常会定义一个有状态的Encoder
实现,当调用visitXXX
方法时,它会将每个对象“推送”到OutputStream
;例如
public class EncoderImpl implements Encoder
private final DataOutputStream daos;
public EncoderImpl(File file) throws IOException
this.daos = new DataOutputStream(new BufferedOutputStream(new FileOutputStream(file)));
public void visitAnimal(Animal a)
daos.writeInt(a.getWeight());
daos.writeUTF(a.getName());
// Write the number of children followed by an encoding of each child animal.
// This allows for easy decoding.
daos.writeInt(a.getChildren().size());
for (Animal child : a.getChildren())
visitAnimal(child);
// TODO: Implement other visitXXX methods.
/**
* Called after visiting each object that requires serializing.
*/
public void done()
daos.close();
【讨论】:
以上是关于如何使用反射递归序列化对象?的主要内容,如果未能解决你的问题,请参考以下文章