gpt4 book ai didi

Java:使用反射转储对象信息时如何避免循环引用?

转载 作者:行者123 更新时间:2023-11-29 04:02:09 25 4
gpt4 key购买 nike

我修改了对象转储方法以避免循环引用导致 StackOverflow 错误。这就是我最终的结果:

//returns all fields of the given object in a string
public static String dumpFields(Object o, int callCount, ArrayList excludeList)
{
//add this object to the exclude list to avoid circual references in the future
if (excludeList == null) excludeList = new ArrayList();
excludeList.add(o);

callCount++;
StringBuffer tabs = new StringBuffer();
for (int k = 0; k < callCount; k++)
{
tabs.append("\t");
}
StringBuffer buffer = new StringBuffer();
Class oClass = o.getClass();
if (oClass.isArray()) {
buffer.append("\n");
buffer.append(tabs.toString());
buffer.append("[");
for (int i = 0; i < Array.getLength(o); i++)
{
if (i < 0) buffer.append(",");
Object value = Array.get(o, i);

if (value != null)
{
if (excludeList.contains(value))
{
buffer.append("circular reference");
}
else if (value.getClass().isPrimitive() || value.getClass() == java.lang.Long.class || value.getClass() == java.lang.String.class || value.getClass() == java.lang.Integer.class || value.getClass() == java.lang.Boolean.class)
{
buffer.append(value);
}
else
{
buffer.append(dumpFields(value, callCount, excludeList));
}
}
}
buffer.append(tabs.toString());
buffer.append("]\n");
}
else
{
buffer.append("\n");
buffer.append(tabs.toString());
buffer.append("{\n");
while (oClass != null)
{
Field[] fields = oClass.getDeclaredFields();
for (int i = 0; i < fields.length; i++)
{
if (fields[i] == null) continue;

buffer.append(tabs.toString());
fields[i].setAccessible(true);
buffer.append(fields[i].getName());
buffer.append("=");
try
{
Object value = fields[i].get(o);
if (value != null)
{
if (excludeList.contains(value))
{
buffer.append("circular reference");
}
else if ((value.getClass().isPrimitive()) || (value.getClass() == java.lang.Long.class) || (value.getClass() == java.lang.String.class) || (value.getClass() == java.lang.Integer.class) || (value.getClass() == java.lang.Boolean.class))
{
buffer.append(value);
}
else
{
buffer.append(dumpFields(value, callCount, excludeList));
}
}
}
catch (IllegalAccessException e)
{
System.out.println("IllegalAccessException: " + e.getMessage());
}
buffer.append("\n");
}
oClass = oClass.getSuperclass();
}
buffer.append(tabs.toString());
buffer.append("}\n");
}
return buffer.toString();
}

该方法最初是这样调用的:
System.out.println(dumpFields(obj, 0, null);

所以,基本上我添加了一个 excludeList ,其中包含所有之前检查过的对象。现在,如果一个对象包含另一个对象并且该对象链接回原始对象,它不应该沿着该链进一步跟随该对象。

但是,我的逻辑似乎有缺陷,因为我仍然陷入无限循环。有谁知道为什么会这样?

编辑:

我仍然收到 StackOverflow 错误
Exception in thread "AWT-EventQueue-0" java.lang.StackOverflowError
at java.lang.reflect.Field.copy(Field.java:127)
at java.lang.reflect.ReflectAccess.copyField(ReflectAccess.java:122)
at sun.reflect.ReflectionFactory.copyField(ReflectionFactory.java:289)
at java.lang.Class.copyFields(Class.java:2739)
at java.lang.Class.getDeclaredFields(Class.java:1743)
at com.gui.ClassName.dumpFields(ClassName.java:627)

我更新的方法:
public static String dumpFields(Object o, int callCount, IdentityHashMap idHashMap)
{
callCount++;

//add this object to the exclude list to avoid circual references in the future
if (idHashMap == null) idHashMap = new IdentityHashMap();
idHashMap.put(o, o);

//setup string buffer and add fields
StringBuffer tabs = new StringBuffer();
for (int k = 0; k < callCount; k++)
{
tabs.append("\t");
}
StringBuffer buffer = new StringBuffer();
Class oClass = o.getClass();
if (oClass.isArray()) {
buffer.append("\n");
buffer.append(tabs.toString());
buffer.append("[");
for (int i = 0; i < Array.getLength(o); i++)
{
if (i < 0) buffer.append(",");
Object value = Array.get(o, i);

if (value != null)
{
if (idHashMap.containsKey(value))
{
buffer.append("circular reference");
}
else if (value.getClass().isPrimitive() || value.getClass() == java.lang.Long.class || value.getClass() == java.lang.String.class || value.getClass() == java.lang.Integer.class || value.getClass() == java.lang.Boolean.class)
{
buffer.append(value);
}
else
{
buffer.append(dumpFields(value, callCount, idHashMap));
}
}
}
buffer.append(tabs.toString());
buffer.append("]\n");
}
else
{
buffer.append("\n");
buffer.append(tabs.toString());
buffer.append("{\n");
while (oClass != null)
{
Field[] fields = oClass.getDeclaredFields();
for (int i = 0; i < fields.length; i++)
{
if (fields[i] == null) continue;

buffer.append(tabs.toString());
fields[i].setAccessible(true);
buffer.append(fields[i].getName());
buffer.append("=");
try
{
Object value = fields[i].get(o);
if (value != null)
{
if (idHashMap.containsKey(value))
{
buffer.append("circular reference");
}
else if ((value.getClass().isPrimitive()) || (value.getClass() == java.lang.Long.class) || (value.getClass() == java.lang.String.class) || (value.getClass() == java.lang.Integer.class) || (value.getClass() == java.lang.Boolean.class))
{
buffer.append(value);
}
else
{
buffer.append(dumpFields(value, callCount, idHashMap));
}
}
}
catch (IllegalAccessException e)
{
System.out.println("IllegalAccessException: " + e.getMessage());
}
buffer.append("\n");
}
oClass = oClass.getSuperclass();
}
buffer.append(tabs.toString());
buffer.append("}\n");
}
return buffer.toString();
}

EDIT2:

您的解决方案似乎非常好。不幸的是,我现在收到了 OutOfMemory 错误,即使我只在一个只有 4 个字段的小类上使用了它。这是我最终得到的代码:
//returns all fields of the given object in a string
public static String dumpFields(Object start)
{
class CallLevel
{
public Object target;
public int level;

public CallLevel(Object target, int level)
{
this.target = target;
this.level = level;
}
}

//create a work list
List<CallLevel> workList = new ArrayList<CallLevel>();
workList.add(new CallLevel(start, 0));

//add this object to the exclude list to avoid circual references in the future
IdentityHashMap idHashMap = new IdentityHashMap();

StringBuffer buffer = new StringBuffer();
while (!workList.isEmpty())
{
CallLevel level = workList.remove(workList.size() - 1);
Object o = level.target;

//add this object to the exclude list to avoid circual references in the future
idHashMap.put(o, o);

//setup string buffer and add fields
StringBuffer tabs = new StringBuffer();
int callCount = level.level;
for (int k = 0; k < callCount; k++)
{
tabs.append("\t");
}
callCount++;
Class oClass = o.getClass();

if (oClass.isArray()) {
buffer.append("\n");
buffer.append(tabs.toString());
buffer.append("[");
for (int i = 0; i < Array.getLength(o); i++)
{
if (i < 0) buffer.append(",");
Object value = Array.get(o, i);

if (value != null)
{
if (idHashMap.containsKey(value))
{
buffer.append("circular reference");
}
else if (value.getClass().isPrimitive() || value.getClass() == java.lang.Long.class || value.getClass() == java.lang.String.class || value.getClass() == java.lang.Integer.class || value.getClass() == java.lang.Boolean.class)
{
buffer.append(value);
}
else
{
workList.add(new CallLevel(value, callCount));
}
}
}
buffer.append(tabs.toString());
buffer.append("]\n");
}
else
{
buffer.append("\n");
buffer.append(tabs.toString());
buffer.append("{\n");
while (oClass != null)
{
Field[] fields = oClass.getDeclaredFields();
for (int i = 0; i < fields.length; i++)
{
if (fields[i] == null) continue;

buffer.append(tabs.toString());
fields[i].setAccessible(true);
buffer.append(fields[i].getName());
buffer.append("=");
try
{
Object value = fields[i].get(o);
if (value != null)
{
if (idHashMap.containsKey(value))
{
buffer.append("circular reference");
}
else if ((value.getClass().isPrimitive()) || (value.getClass() == java.lang.Long.class) || (value.getClass() == java.lang.String.class) || (value.getClass() == java.lang.Integer.class) || (value.getClass() == java.lang.Boolean.class))
{
buffer.append(value);
}
else
{
workList.add(new CallLevel(value, callCount));
}
}
}
catch (IllegalAccessException e)
{
System.out.println("IllegalAccessException: " + e.getMessage());
}
buffer.append("\n");
}
oClass = oClass.getSuperclass();
}
buffer.append(tabs.toString());
buffer.append("}\n");
}
}
return buffer.toString();
}

这么小的对象不应该导致 OutOfMemory 错误。

有任何想法吗?

EDIT3:

改写版:
public static String dumpFields(Object start)
{
class CallLevel
{
public Object target;
public int level;

public CallLevel(Object target, int level)
{
this.target = target;
this.level = level;
}
}

//create a work list
List<CallLevel> workList = new ArrayList<CallLevel>();
workList.add(new CallLevel(start, 0));

//create an identity map for object comparison
IdentityHashMap idHashMap = new IdentityHashMap();

//setup a string buffer to return
StringBuffer buffer = new StringBuffer();
while (!workList.isEmpty())
{
CallLevel level = workList.remove(workList.size() - 1);
Object o = level.target;

//add this object to the exclude list to avoid circual references in the future
idHashMap.put(o, o);

//set string buffer for tabs
StringBuffer tabs = new StringBuffer();
int callCount = level.level;
for (int k = 0; k < callCount; k++)
{
tabs.append("\t");
}

//increment the call count for future calls
callCount++;

//set the class for this object
Class oClass = o.getClass();

//if this is an array, dump it's elements, otherwise dump the fields of this object
if (oClass.isArray()) {
buffer.append("\n");
buffer.append(tabs.toString());
buffer.append("[");
for (int i = 0; i < Array.getLength(o); i++)
{
if (i < 0) buffer.append(",");
Object value = Array.get(o, i);

if (value != null)
{
if (value.getClass().isPrimitive())
{
buffer.append(value);
}
else if (idHashMap.containsKey(value))
{
buffer.append("circular reference");
}
else
{
workList.add(new CallLevel(value, callCount));
}
}
}
buffer.append(tabs.toString());
buffer.append("]\n");
}
else
{
buffer.append("\n");
buffer.append(tabs.toString());
buffer.append("{\n");
while (oClass != null)
{
Field[] fields = oClass.getDeclaredFields();
for (int i = 0; i < fields.length; i++)
{
//make sure this field exists
if (fields[i] == null) continue;

//ignore static fields
if (!Modifier.isStatic(fields[i].getModifiers()))
{
buffer.append(tabs.toString());
fields[i].setAccessible(true);
buffer.append(fields[i].getName());
buffer.append("=");
try
{
Object value = fields[i].get(o);
if (value != null)
{
if (fields[i].getType().isPrimitive())
{
buffer.append(value);
}
else if (idHashMap.containsKey(value))
{
buffer.append("circular reference");
}
else
{
workList.add(new CallLevel(value, callCount));
}
}
}
catch (IllegalAccessException e)
{
System.out.println("IllegalAccessException: " + e.getMessage());
}
buffer.append("\n");
}
}
oClass = oClass.getSuperclass();
}
buffer.append(tabs.toString());
buffer.append("}\n");
}
}
return buffer.toString();
}

我假设 getClass().isPrimitive() 仍然适用于数组索引,但我可能错了。如果是这样,您将如何处理?此外,其他 getClass() == Integer 等检查对我来说似乎是不必要的,因为 isPrimitive() 检查应该解决这个问题,对吧?

无论如何,在一个简单的对象上使用时,我仍然会遇到内存不足错误:
Exception in thread "AWT-EventQueue-0" java.lang.OutOfMemoryError: Java heap space
at java.util.Arrays.copyOfRange(Arrays.java:3209)
at java.lang.String.<init>(String.java:215)
at java.lang.StringBuffer.toString(StringBuffer.java:585)
at com.gui.ClassName.dumpFields(ClassName.java:702)
at com.gui.ClassName.setTextArea(ClassName.java:274)
at com.gui.ClassName.access$8(ClassName.java:272)
at com.gui.ClassName$1.valueChanged(ClassName.java:154)
at javax.swing.JList.fireSelectionValueChanged(JList.java:1765)
at javax.swing.JList$ListSelectionHandler.valueChanged(JList.java:1779)
at javax.swing.DefaultListSelectionModel.fireValueChanged(DefaultListSelectionModel.java:167)
at javax.swing.DefaultListSelectionModel.fireValueChanged(DefaultListSelectionModel.java:147)
at javax.swing.DefaultListSelectionModel.fireValueChanged(DefaultListSelectionModel.java:194)
at javax.swing.DefaultListSelectionModel.changeSelection(DefaultListSelectionModel.java:388)
at javax.swing.DefaultListSelectionModel.changeSelection(DefaultListSelectionModel.java:398)
at javax.swing.DefaultListSelectionModel.setSelectionInterval(DefaultListSelectionModel.java:442)
at javax.swing.JList.setSelectedIndex(JList.java:2179)
at com.gui.ClassName$1.valueChanged(ClassName.java:138)
at javax.swing.JList.fireSelectionValueChanged(JList.java:1765)
at javax.swing.JList$ListSelectionHandler.valueChanged(JList.java:1779)
at javax.swing.DefaultListSelectionModel.fireValueChanged(DefaultListSelectionModel.java:167)
at javax.swing.DefaultListSelectionModel.fireValueChanged(DefaultListSelectionModel.java:137)
at javax.swing.DefaultListSelectionModel.setValueIsAdjusting(DefaultListSelectionModel.java:668)
at javax.swing.JList.setValueIsAdjusting(JList.java:2110)
at javax.swing.plaf.basic.BasicListUI$Handler.mouseReleased(BasicListUI.java:2783)
at java.awt.AWTEventMulticaster.mouseReleased(AWTEventMulticaster.java:273)
at java.awt.Component.processMouseEvent(Component.java:6263)
at javax.swing.JComponent.processMouseEvent(JComponent.java:3255)
at java.awt.Component.processEvent(Component.java:6028)
at java.awt.Container.processEvent(Container.java:2041)
at java.awt.Component.dispatchEventImpl(Component.java:4630)
at java.awt.Container.dispatchEventImpl(Container.java:2099)
at java.awt.Component.dispatchEvent(Component.java:4460)

最佳答案

+1 使用 IdentityHashMap解决问题。

原因是您的方法当前取决于每个访问对象的类如何实现 equals , 自 List.contains(Object)使用 equals作为比较的依据。如果一个类(class)的 equals()方法被破坏,并错误地返回 false即使将自身作为比较对象传递,您也会得到一个无限循环,因为调用 List.contains始终返回 false 并且始终针对该类型的对象遍历该对象子图。

此外,如果您有两个或多个对象是不同的实例,但在值上被视为相等(即 equals 返回 true),则只会写出其中一个。这是可取的还是问题取决于您的用例。

使用 IdentityHashMap将避免这两个问题。

顺便说一句 - 如果你想根据调用深度缩进,不要忘记递增 callCount递归调用 dumpFields .

编辑:我认为代码工作正常。问题是您确实遇到了堆栈溢出。如果您有一个大型对象图,就会发生这种情况。例如,想象一个包含 3000 个元素的链表。这将涉及 3000 次递归调用,我很确定这会破坏默认堆栈大小的堆栈。

要解决此问题,您可以将堆栈的大小 (vmarg -Xss) 增加到足够大以处理您预期的对象图大小(不是一个强大的解决方案!),或者用显式数据结构替换堆栈的使用。

而不是堆栈,创建一个工作列表。此列表包含您已经看到但尚未处理的对象。您只需将对象添加到您的工作列表中,而不是递归调用 dumpFields。该方法的主体是一个 while 循环,只要列表中有项目,它就会迭代。

例如。

class CallLevel
{
CallLevel(Object target, int level) {
this.target = target; this.level = level;
}
Object target;
int level;
}
public static String dumpFields(Object start)
{
List<CallLevel> workList = new ArrayList<CallLevel>();
workList.add(new Calllevel(start,0));
Map idHashMap = new IdentityHashMap();

while (!workList.isEmpty()) {
CallLevel level = workList.removeAt(workList.size()-1);
Object o = level.object;
//add this object to the exclude list to avoid circual references in the future
idHashMap.put(, o);

//setup string buffer and add fields
StringBuffer tabs = new StringBuffer();
int callCount = level.level;
for (int k = 0; k < callCount; k++)
{
tabs.append("\t");
}
callCount++;
StringBuffer buffer = new StringBuffer();
Class oClass = o.getClass();
if (oClass.isArray()) {
buffer.append("\n");
buffer.append(tabs.toString());
buffer.append("[");
for (int i = 0; i < Array.getLength(o); i++)
{
if (i < 0) buffer.append(",");
Object value = Array.get(o, i);

if (value != null)
{
if (idHashMap.containsKey(value))
{
buffer.append("circular reference");
}
else if (value.getClass().isPrimitive() || value.getClass() == java.lang.Long.class || value.getClass() == java.lang.String.class || value.getClass() == java.lang.Integer.class || value.getClass() == java.lang.Boolean.class)
{
buffer.append(value);
}
else
{
workList.add(new Calllevel(value, callCount));
}
}
}
buffer.append(tabs.toString());
buffer.append("]\n");
}
else
{
buffer.append("\n");
buffer.append(tabs.toString());
buffer.append("{\n");
while (oClass != null)
{
Field[] fields = oClass.getDeclaredFields();
for (int i = 0; i < fields.length; i++)
{
if (fields[i] == null) continue;

buffer.append(tabs.toString());
fields[i].setAccessible(true);
buffer.append(fields[i].getName());
buffer.append("=");
try
{
Object value = fields[i].get(o);
if (value != null)
{
if (idHashMap.containsKey(value))
{
buffer.append("circular reference");
}
else if ((value.getClass().isPrimitive()) || (value.getClass() == java.lang.Long.class) || (value.getClass() == java.lang.String.class) || (value.getClass() == java.lang.Integer.class) || (value.getClass() == java.lang.Boolean.class))
{
buffer.append(value);
}
else
{
workList.add(new CallLevel(value, callCount));
}
}
}
catch (IllegalAccessException e)
{
System.out.println("IllegalAccessException: " + e.getMessage());
}
buffer.append("\n");
}
oClass = oClass.getSuperclass();
}
buffer.append(tabs.toString());
buffer.append("}\n");
}
return buffer.toString();

编辑2:
我刚刚运行了代码,看看会发生什么。完成这项工作需要进行 3 项主要更改:
  • 原始类型的测试应该是第一个测试(3 个 if 语句中的第一个)。第二个 else if 是针对排除映射的测试。
  • 原始类型的测试需要包括对所有原始类的检查。您目前有一些测试,但缺少浮点、 double 、字节、短和长。
  • 跳过静态字段,检查 Modifier.isStatic(field.getModifiers()) .

  • 原始测试应该首先发生的原因是,通过反射,原始类型使用相应类的新实例进行装箱(例如,对于双字段,创建新的 Double - 这是一种简化 - JDK 实际上会重用一些对象,请参阅 Integer.valueOf() 的来源,但一般来说,当一个基元被装箱时会创建一个新对象。)由于这些基元生成唯一的新对象,因此根据排除映射检查这些几乎没有意义。因此,将primite测试放在首位。顺便提一下,支票 value.getClass().isPrimitive()将始终返回 false - 装箱类型绝不是原始类型。您可以改为使用字段的声明类型,例如 field.getType().isPrimitive() .

    针对盒装原语类别的测试必须包括对 的测试。全部 盒装原始类。如果没有,那么这些新的装箱对象将继续创建,发现尚未被排除(因为它们是新实例)并添加到工作列表中。这成为一个失控的问题 - 像 MAX_VALUE 这样的静态公共(public)最终常量会导致生成更多实例,这些实例被添加到列表中,并且这些对象的字段的反射会导致更多值等......修复是确保所有原始类型都是测试(或在字段类型上使用 isPrimitive,而不是返回的对象类型。)

    不输出静态字段将作为上述问题的辅助解决方案,但更重要的是,它将避免您的输出因不必要的细节而变得困惑。

    关于Java:使用反射转储对象信息时如何避免循环引用?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/2746267/

    25 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com