17

収集されたアイテムのグラフのどこかにそれ自体への参照がある場合、コレクションを文字列化することは無限ループに入る可能性があります。以下の例を参照してください。

はい、良いコーディング手法はそもそもこれを防ぐはずですが、とにかく、私の質問は次のとおりです。この状況で再帰を検出する最も効率的な方法は何ですか?

1つのアプローチは、スレッドローカルでセットを使用することですが、それは少し重いようです。

public class AntiRecusionList<E> extends ArrayList<E> {
  @Override
  public String toString() {
    if (  /* ???? test if "this" has been seen before */ ) {
        return "{skipping recursion}";
    } else {
        return super.toString();
    }
  }
}


public class AntiRecusionListTest {
  @Test
  public void testToString() throws Exception {
      AntiRecusionList<AntiRecusionList> list1 = new AntiRecusionList<>();
      AntiRecusionList<AntiRecusionList> list2 = new AntiRecusionList<>();
      list2.add(list1);
      list1.add(list2);
      list1.toString();  //BOOM !
  }
}
4

9 に答える 9

11

危険なグラフを反復処理する必要がある場合、通常、デクリメントカウンターを使用して関数を作成します。

例えば ​​:

public String toString(int dec) {
    if (  dec<=0 ) {
        return "{skipping recursion}";
    } else {
        return super.toString(dec-1);
    }
}

public String toString() {
    return toString(100);
}

あなたがすでにそれを知っているように、私はそれを主張しません、しかしそれは契約toString()が短くて予測可能でなければならないことを尊重しません。

于 2012-07-02T19:52:29.540 に答える
5

私が質問で言及したスレッドローカルビット:

public class AntiRecusionList<E> extends ArrayList<E> {


private final ThreadLocal<IdentityHashMap<AntiRecusionList<E>, ?>> fToStringChecker =
        new ThreadLocal<IdentityHashMap<AntiRecusionList<E>, ?>>() {
            @Override
            protected IdentityHashMap<AntiRecusionList<E>, ?> initialValue() {
                return new IdentityHashMap<>();
            }
        };    

@Override
public String toString() {
    boolean entry = fToStringChecker.get().size() == 0;
    try {
        if (fToStringChecker.get().containsKey(this)/* test if "this" has been seen before */) {
            return "{skipping recursion}";
        } else {
            fToStringChecker.get().put(this, null);
            entry = true;
        }
        return super.toString();
    } finally {
        if (entry)
            fToStringChecker.get().clear();
    }
}
}
于 2012-07-02T20:02:19.123 に答える
3

この問題はコレクションに固有のものではなく、二重リンクリストなどの循環参照を持つオブジェクトのグラフで発生する可能性があります。

私は正しい方針だと思います:それがサイクルを持つオブジェクトグラフの一部である可能性がある場合toString()、あなたのクラスのメソッドはその子を呼び出さない/参照されるべきではありません。toString()他の場所では、完全グラフの文字列表現を生成する特別なメソッド(おそらく静的、おそらく補助クラスとして)を持つことができます。

于 2012-07-02T20:00:41.743 に答える
3

IDハッシュセットを取得するtoStringを作成できます。

public String toString() {
   return toString(Collections.newSetFromMap(new IdentityHashMap<Object, Boolean>()));
}

private String toString(Set<Object> seen) {
   if (seen.add(this)) {
      // to string this
   } else {
      return "{this}";
   }
}
于 2012-07-02T20:04:02.213 に答える
3

ApacheCommonsLangのToStringBuilderを使用することをお勧めします。内部的には、ThreadLocal Mapを使用して、「循環オブジェクト参照を検出し、無限ループを回避します」。

于 2013-04-05T15:56:23.517 に答える
2

次のように、再帰を常に追跡できます(スレッドの問題は考慮されていません)。

public static class AntiRecusionList<E> extends ArrayList<E> {
   private boolean recursion = false;

   @Override
    public String toString() {
         if(recursion){
               //Recursion's base case. Just return immediatelly with an empty string
               return "";
         }
         recursion = true;//start a perhaps recursive call
         String result = super.toString();
         recursion = false;//recursive call ended
         return result;
   }
}
于 2012-07-02T20:21:11.620 に答える
1

最も簡単な方法:toString()コレクションやマップの要素を呼び出さないでください。を印刷し[]て、それがコレクションまたはマップであることを示し、完全に繰り返さないようにします。これは、無限再帰に陥らないようにする唯一の防弾方法です。

一般的なケースでは、どの要素が別のオブジェクト内CollectionまたはMap別のオブジェクト内にあるかを予測できず、依存関係グラフが非常に複雑になり、オブジェクトグラフでサイクルが発生するという予期しない状況が発生する可能性があります。

どのIDEを使用していますか?toString()Eclipseには、コードジェネレーターを介してメソッドを生成するときにこのケースを明示的に処理するオプションがあるためです。これは、属性[]に含まれる要素の数に関係なく、属性がnull以外のコレクションまたはマップ印刷である場合に使用します。

于 2012-07-02T19:50:55.597 に答える
1

やりすぎたい場合は、toString()を呼び出すたびに、ネストされたコレクションを追跡するアスペクトを使用できます。

public aspect ToStringTracker() {
  Stack collections = new Stack();

  around( java.util.Collection c ): call(String java.util.Collection+.toString()) && target(c) {
    if (collections.contains(c)) { return "recursion"; }
    else { 
      collections.push(c);
      String r = c.toString(); 
      collections.pop();
      return r;
    }
  }
}

これをEclipseに投入せずに構文を100%使用することは決してありませんが、あなたはその考えを理解していると思います

于 2012-07-02T20:50:37.737 に答える
0

toStringで例外を作成し、スタックトレースを利用して、スタックのどこにいるかを知ることができます。再帰呼び出しがあることがわかります。一部のフレームワークはこのようにします。

@Override
public String toString() {
    // ... 
    Exception exception = new Exception();
    StackTraceElement[] stackTrace = exception.getStackTrace();
    // now you analyze the array: stack trace elements have 
    // 4 properties: check className, lineNumber and methodName.
    // if analyzing the array you find recursion you stop propagating the calls
    // and your stack won't explode    
    //...    

}
于 2012-07-02T20:02:54.790 に答える