我修改了一个对象转储方法,以避免导致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);
所以,基本上我添加了一个包含所有previousely检查对象的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)
答案 0 :(得分:4)
+1使用IdentityHashMap
来解决问题。
原因是您的方法当前取决于每个访问对象的类如何实现equals
,因为List.contains(Object)使用equals
作为比较的基础。如果类的equals()
方法被破坏,并且即使将自身作为比较对象传递时也错误地返回false
,那么您将获得无限循环,因为对List.contains
的调用始终返回false并且对象子图总是遍历该类型的对象。
此外,如果您有两个或多个不同实例的对象,但被视为等于值(即等于返回true),则只会写出其中一个。这是否可取或问题取决于您的使用案例。
使用IdentityHashMap
可以避免这两个问题。
抛开 - 如果你想根据调用深度缩进,不要忘记递归 callCount
递归调用{/ 1}}。
要解决此问题,您可以将堆栈的大小(vmarg -Xss)增加到足以处理预期的对象图形大小(不是一个强大的解决方案!),或者使用显式数据结构替换堆栈的使用
创建工作清单而不是堆栈。此列表包含您已看到但尚未处理的对象。您只需将对象添加到工作列表中,而不是递归调用dumpFields。该方法的主体是一个while循环,只要列表中有项目就会迭代。
E.g。
dumpFields
EDIT2: 我刚刚运行代码看看会发生什么。完成这项工作需要进行3项主要更改:
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();
。原始测试首先应该发生的原因是,使用反射时,原始类型使用相应类的新实例加框(例如,对于双字段,创建新的Modifier.isStatic(field.getModifiers())
- 这是一个简单的 - JDK实际上会重用一些对象,查看Double
的源代码,但一般情况下,当一个基元被装箱时会创建一个新对象。)由于这些原始元素生成了唯一的新对象,因此对于排除映射几乎没有必要检查它们。因此,首先进行原始测试。顺便提一下,检查Integer.valueOf()
将始终返回false - 盒装类型永远不是基本类型。您可以使用声明的字段类型,例如value.getClass().isPrimitive()
。
针对盒装基元类的测试必须包括所有盒装原始类的测试。如果没有,那么将继续创建这些新的盒装对象,发现它们不被排除(因为它们是新实例)并添加到工作列表中。这成为一个失控的问题 - 像MAX_VALUE这样的静态公共最终常量导致生成更多实例,这些实例被添加到列表中,并且这些对象的字段的反映会导致更多的值等...修复是为了确保所有基本类型都是测试(或在字段类型上使用isPrimitive,而不是返回的对象类型。)
不输出静态字段将作为上述问题的辅助修复,但更重要的是它可以避免输出混乱的不必要的细节。