1

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:

あなたの解決策は本当に良いようです。残念ながら、フィールドが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)
4

1 に答える 1

4

IdentityHashMap問題を解決するために使用するための+1 。

equalsこれは、List.contains(Object)equalsが比較の基礎として使用するため、メソッドは現在、訪問した各オブジェクトのクラスの実装方法に依存しているためです。クラスのequals()メソッドが壊れていて、falseそれ自体を比較オブジェクトとして渡しても誤って返される場合、への呼び出しはList.contains常にfalseを返し、そのオブジェクトサブグラフは常にそのタイプのオブジェクトに対してトラバースされるため、無限ループが発生します。

さらに、別個のインスタンスであるが、値が等しいと見なされる(つまり、equalsがtrueを返す)オブジェクトが2つ以上ある場合、これらの1つだけが書き出されます。これが望ましいか問題かは、ユースケースによって異なります。

を使用する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();

EDIT2:コードを実行して何が起こるかを確認しました。この作業を行うために必要な3つの主な変更があります。

  1. プリミティブ型のテストは、最初のテスト(3つのifステートメントの最初のテスト)である必要があります。2番目のテストは、除外マップに対するテストです。
  2. プリミティブ型のテストには、すべてのプリミティブクラスのチェックを含める必要があります。現在、いくつかのテストがありますが、float、double、byte、short、longがありません。
  3. 静的フィールドをスキップして、チェックしModifier.isStatic(field.getModifiers())ます。

プリミティブテストを最初に行う必要がある理由は、リフレクションを使用すると、対応するクラスの新しいインスタンスを使用してプリミティブ型がボックス化されるためです(たとえば、ダブルフィールドの場合、新しいDoubleものが作成されます-これは単純化です-JDKは実際にいくつかのオブジェクトを再利用します。を参照してください。ソースはInteger.valueOf()、一般に、プリミティブがボックス化されたときに作成されます。)これらのプリミティブは一意の新しいオブジェクトを生成するため、除外マップに対してこれらをチェックする意味はほとんどありません。したがって、プリマイトテストを最初に置きます。ちなみに、チェックvalue.getClass().isPrimitive()は常にfalseを返します-ボックス化された型は決してプリミティブ型ではありません。代わりに、宣言されたタイプのフィールドを使用できますfield.getType().isPrimitive()

ボックス化されたプリミティブのクラスに対するテストには、すべてのボックス化されたプリミティブクラスのテストが含まれている必要があります。そうでない場合、これらの新しいボックス化されたオブジェクトは引き続き作成され、(新しいインスタンスであるため)まだ除外されていないことがわかり、ワークリストに追加されます。これは暴走する問題になります-MAX_VALUEのような静的なpublicfinal定数は、リストに追加されるより多くのインスタンスの生成を引き起こし、それらのオブジェクトのフィールドの反映はより多くの値などを引き起こします...修正はすべてのプリミティブ型がテスト済み(または、返されたオブジェクトタイプではなく、フィールドタイプでisPrimitiveを使用します)。

静的フィールドを出力しないことは、上記の問題に対する補助的な修正として役立ちますが、さらに重要なことに、出力が不要な詳細で乱雑になるのを防ぎます。

于 2010-04-30T19:23:50.343 に答える