Java:用反射转储对象信息时如何避免循环引用?
我修改了对象转储方法以避免循环引用导致 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);
所以,基本上我添加了一个包含所有先前检查的对象的排除列表。现在,如果一个对象包含另一个对象,并且该对象链接回原始对象,则它不应沿着该链进一步跟随该对象。
然而,我的逻辑似乎有缺陷,因为我仍然陷入无限循环。有谁知道为什么会发生这种情况?
编辑:
我仍然收到 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();
}
编辑2:
您的解决方案看起来非常好。不幸的是,尽管我只在只有 4 个字段的小类上使用过它,但现在我收到了 OutOfMemory 错误。这是我最终得到的代码:
//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)
I've modified an object dumping method to avoid circual references causing a StackOverflow error. This is what I ended up with:
//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();
}
The method is initially called like this:
System.out.println(dumpFields(obj, 0, null);
So, basically I added an excludeList which contains all the previousely checked objects. Now, if an object contains another object and that object links back to the original object, it should not follow that object further down the chain.
However, my logic seems to have a flaw as I still get stuck in an infinite loop. Does anyone know why this is happening?
EDIT:
I'm still getting an StackOverflow error
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)
My updated method:
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:
Your solution seems really good. Unfortunately I am getting an OutOfMemory error now even though I've only used it on a tiny class with only 4 fields. This is the code I ended up with:
//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();
}
It shouldn't cause an OutOfMemory error with such a small object.
Any ideas?
EDIT3:
Rewritten version:
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();
}
I assumed that the getClass().isPrimitive() would still work for an array index, but I might be wrong. If so, how would you handle this? Also, the other getClass() == Integer, etc. checks seemed unnecessary to me as the isPrimitive() check should take care of this, right?
Anyway, I'm still getting the out of memory error when used on a simple object:
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)
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(1)
+1 使用
IdentityHashMap
解决问题。原因是您的方法当前取决于每个访问对象的类如何实现
equals
,因为List.contains(Object) 使用equals
作为基础进行比较。如果类的equals()
方法被破坏,并且即使将其自身作为比较对象传递,也会错误地返回false
,那么您将得到一个无限循环,因为对的调用>List.contains
始终返回 false,并且始终针对该类型的对象遍历该对象子图。此外,如果您有两个或多个对象是不同的实例,但被认为值相等(即 equals 返回 true),则只会写出其中之一。这是否是可取的还是一个问题取决于您的用例。
使用
IdentityHashMap
将避免这两个问题。旁白 - 如果您想根据调用深度缩进,请不要忘记在递归调用dumpFields
时增加callCount
。编辑:我认为代码工作正常。问题是你确实遇到了堆栈溢出。如果您有一个大的对象图,就会发生这种情况。例如,想象一个包含 3000 个元素的链表。这将涉及 3000 次递归调用,我很确定这会破坏默认堆栈大小的堆栈。
要解决此问题,您可以将堆栈的大小 (vmarg -Xss) 增加到足以处理预期的对象图大小(不是一个可靠的解决方案!),或者使用显式数据结构替换堆栈的使用。
创建工作列表而不是堆栈。此列表包含您已经看到但尚未处理的对象。您只需将对象添加到工作列表中,而不是递归调用 dumpFields。该方法的主体是一个 while 循环,只要列表中存在项目,它就会进行迭代。
例如
编辑2:
我刚刚运行了代码来看看会发生什么。要完成这项工作,需要进行 3 个主要更改:
Modifier.isStatic(field.getModifiers())
。原始测试应该首先发生的原因是,通过反射,原始类型使用相应类的新实例进行装箱(例如,对于双字段,创建一个新的
Double
- 这是一种简化 - JDK 实际上会重用一些对象,请参阅 Integer.valueOf() 的源代码,但一般来说,当基元装箱时会创建一个新对象。)由于这些基数生成唯一的新对象,因此没有什么意义根据排除地图检查这些。因此,首先进行原始测试。顺便说一句,检查value.getClass().isPrimitive()
将始终返回 false - 装箱类型永远不是原始类型。您可以改为使用字段的声明类型,例如field.getType().isPrimitive()
。针对装箱基元类的测试必须包括对所有装箱基元类的测试。如果没有,则将继续创建这些新的装箱对象,发现它们尚未被排除(因为它们是新实例)并添加到工作列表中。这成为一个失控的问题 - 像 MAX_VALUE 这样的静态公共最终常量会导致生成更多实例,这些实例被添加到列表中,并且这些对象的字段的反射会导致更多值等......解决方法是确保所有原始类型都是测试(或在字段类型上使用 isPrimitive,而不是返回的对象类型。)
不输出静态字段将作为对上述问题的辅助修复,但更重要的是,它将避免您的输出因不必要的细节而混乱。
+1 for using
IdentityHashMap
to fix the problem.The reason for this is that your method is currently dependent upon how each visited object's class implements
equals
, since List.contains(Object) usesequals
as the basis for comparison. If a class'sequals()
method is broken, and incorrectly returnsfalse
even when passed itself as the comparison object, then you will get an infinite loop because the call toList.contains
always returns false and that object subgraph is always traversed for that type of object.Additionally, if you have two or more objects that are distinct instances, but are considered equal by value (i.e. equals returns true), only one of these will be written out. Whether this is desirable or a problem depends upon your use case.
Using an
IdentityHashMap
will avoid both of these problems.An aside - if you want to indent according to the call depth, don't forget to incrementcallCount
on recursive calls todumpFields
.EDIT: I think the code is working correctly. The problem is that you really are getting a stack overflow. This will happen if you have a large object graph. For example, imagine a linked list of 3000 elements. That will involve 3000 recursive calls, which I'm pretty sure will blow the stack with the default stack size.
To fix this, you either increase the size of the stack (vmarg -Xss) to be large enough to handle your anticipated object graph size (not a robust solution!) or, replace use of the stack with an explicit data structure.
Instead of the stack, create a work list. This list holds objects that you've seen but not yet processed. Rather than recursively call dumpFields, you simply add the object to your work list. The main body of the method is a while loop that iterates as long as there are items in the list.
E.g.
EDIT2:
I've just ran the code to see what happens. There are 3 main changes needed to make this work:
Modifier.isStatic(field.getModifiers())
.The reason primitive tests should happen first is that with reflection, the primitive type is boxed using a new instance of the corresponding class (e.g. for a double field a new
Double
is created - this is a simpliciation - the JDK will actually reuse some objects, see the sources forInteger.valueOf()
but in general a new object is created when a primitive is boxed.) As these primities generate unique new objects, there is little point checking these against the exclude map. Hence, put the primite test first. Incidentally, the checkvalue.getClass().isPrimitive()
will always return false - the boxed type is never a primitive type. You can instead use the declared type of the field, e.g.field.getType().isPrimitive()
.The test against classes of boxed primitives, must include tests for all boxed primive classes. If it doesn't, then these new boxed objects will continue to be created, found not to be already excluded (since they are new instances) and added to the work list. This becomes a runaway problem - the static public final constants like MAX_VALUE cause generation of more instances, which are added to the list, and reflection of the fields of those objects cause more values etc... The fix is to ensure all primitive types are tested for (or use the isPrimitive on the field type, not the returned object type.)
Not outputting static fields will serve as an ancilliary fix to the problem above, but more importantly it will save your output from being cluttered with unnecessary details.