对象在内存中的字段和值遍历在Java中实现

huangapple 未分类评论46阅读模式
英文:

Object's in memory Field and value traversal in Java

问题

我有一个情景,需要遍历内存中的一个复杂对象。我需要遍历整个树的键和值,并获取所需的内容。是否有方法可以做到这一点?它不能被序列化为JSON,这是一个大问题。所以唯一的方法就是在对象加载到内存时遍历它。

对象实际上是这样的。它有很多深层嵌套,GSON或任何其他库都不能将其序列化为JSON。

英文:

I have a scenario where I need to traverse a complex object in memory.I need to traverse the entire tree's key and value and to the desired stuff. Is there any way to do it ? It cant be serialized to JSON ,which is the big issue. So only way is to traverse the object when its loaded into the memory.

对象在内存中的字段和值遍历在Java中实现

Object actually looks like this.It has may deep nesting and GSON or any other library cant serialize it to JSON.

答案1

得分: 0

非常,非常复杂。

给定任何对象,获取其所有字段:

Class<?> c = obj.getClass();
var fields = new ArrayList<Field>();
while (c != null) {
   for (Field f : c.getDeclaredFields()) {
       if (Modifier.isStatic(f.getModifiers())) continue;
       f.setAccessible(true);
       fields.add(f);
    }
    c = c.getSuperclass();
}

看起来很费力,但你必须使用declaredFields变体来获取私有字段,而且它不会自动遍历超类型层次结构。一旦你有了字段列表,你可以使用通常的field.get(obj)来获取设置在该字段中的对象。然后... 递归地将相同的算法应用于这些对象,以构建这样一个层次结构。

问题:数组

数组需要特殊关注。你不能只是询问它的字段;它将没有任何字段。因此,在使用field.get(obj)之后,你需要这样做:

if (a.getClass().isArray()) {
    int len = Array.getLength(a);
    for (int i = 0; i < len; i++) {
        Object actualContent = Array.get(a, i);
    }
}

然后像正常情况一样处理'actualContent'的每个值(即在其上递归运行此算法)。

问题:要打印的内容太多了!

是的,确实 - 一个字符串本身就有一个char[]。你可能想要进行一些instanceof检查并为已知类型(如String)'硬编码'自定义打印机。

问题:JDK11+会警告某些内容。

是的,最终将不再允许这样做,除非涉及调试代理代理,这是一个更加复杂的问题。在JDK14中,尽管会打印出警告,但目前仍然可以工作。

问题:循环/自我引用

对象可以在循环中引用自己。例如:

List<Object> list = new ArrayList<Object>();
list.add(list);

// 或者一个循环:

List<Object> list1 = new ArrayList<Object>();
List<Object> list2 = new ArrayList<Object>();
list1.add(list2);
list2.add(list1);

这将导致你的打印代码永远循环。解决这个问题的方法是跟踪对象标识,如果发现你已经“看到”了一个对象,则根本不要打印它,只需打印一些引用。这还需要打印每个对象的引用。要获取标识对象的数字,请使用System.identityHashCode,这可以保证为你提供一个非常好的尝试来生成唯一的ID(但并不能保证每个对象一定会获得唯一的ID)。要检查是否已经看到一个对象,使用IdentityHashMap,其中映射的值部分并不重要(如果有IdentityHashSet,我会告诉你使用它,但实际上没有,只需映射到“”并检查键的存在即可)。

考虑所有这些问题,你也可以编写一个深度对象打印器。

或者只是依赖于你的IDE来完成这个任务,可能有更好的解决方案来解决你要解决的问题:我知道!我会打印整个对象的原始内容,递归进行!- 但你的问题并没有包括为什么要这样做。

英文:

Very, very complicated.

Given any object, get all its fields:

Class&lt;?&gt; c = obj.getClass();
var fields = new ArrayList&lt;Field&gt;();
while (c != null) {
   for (Field f : c.getDeclaredFields()) {
       if (Modifier.isStatic(f.getModifiers())) continue;
       f.setAccessible(true);
       fields.add(f);
    }
    c = c.getSuperclass();
}

Seems like a lot of work, but you have to use the declaredFields variant to get at private fields as well, and it does not automatically walk the supertype hierarchy for you. Once you have that list, you can just get the object set for that field using the usual field.get(obj). Then.. apply the same algorithm recursively to THOSE objects to build such a hierarchy.

Problem: Arrays

arrays require special care. You can't just ask it for its fields; it will have none. Therefore, you need this, after using field.get(obj):

if (a.getClass().isArray()) {
    int len = Array.getLength(a);
    for (int i = 0; i &lt; len; i++) {
        Object actualContent = Array.get(a, i);
    }
}

and then treat each value of 'actualContent' as normal (i.e. recursively run this algorithm on it).

Problem: That's a ton of stuff to print!

Yes, it is – a string alone has a char[] inside. You may want to do some instanceof checks and 'hardcode' custom printers for known types, such as String.

Problem: JDK11+ warns about something.

Yes, eventually this is no longer allowed and there will be no way to do this without involving hooking up debugger agents, which is a much more complicated can of worms. As of JDK14 this still works though, even if it prints a warning.

Problem: Loops/self-referencing

It is possible for objects to refer to themselves in a loop. For example:

List&lt;Object&gt; list = new ArrayList&lt;Object&gt;();
list.add(list);

// or a cycle:

List&lt;Object&gt; list1 = new ArrayList&lt;Object&gt;();
List&lt;Object&gt; list2 = new ArrayList&lt;Object&gt;();
list1.add(list2);
list2.add(list1);

which would cause your print code to loop forever. The solution to this is to track object identity, and if you find an object you've already 'seen', don't print it at all, just print some reference. This then also requires that you print the reference of each object. To get a number that identifies an object, use System.identityHashCode, which is guaranteed to do a really good attempt at giving you a unique id (but it is not 100% guaranteed that each object will necessarily get a unique ID). To check if you already saw an object, use an IdentityHashMap, where the map's value part doesn't matter (if there was an IdentityHashSet I'd have told you to use that, but there isn't. just map to "" and check for the existence of the key, instead).

Consider all these things and you too can write a deep-diving object printer.

Or just rely on your IDE to do this, there probably are way better solutions to whatever problem made you go: I know! I'll print the entire raw content of this object, recursively! – but your question doesn't include why you'd want this.

huangapple
  • 本文由 发表于 2020年4月7日 06:00:08
  • 转载请务必保留本文链接:https://java.coder-hub.com/61069630.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定