2012-07-02 16 views
16

収集されたアイテムのグラフのどこかに参照がある場合は、コレクションの文字列を無限ループにすることができます。以下の例を参照してください。toString()の無限再帰を防ぐ最も効率的な方法は?

はい、良いコーディング慣行は、最初にこれを防ぐ必要がありますが、とにかく、私の質問です:この状況で再帰を検出する最も効率的な方法は何ですか?

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 ! 
    } 
} 
+3

[Lombok](http://projectlombok.org/)をご覧ください。 '@ToString(exclude = {" fields "、" you "、" dont "、" want "})'でクラスに注釈を付けることができます。 :) – elias

+1

このようなことは絶対に実行しないでください。しかし、log4j(または類似の)デバッグをオンに切り替えると発生する可能性があるため、このようなことは本当の痛みになる可能性があります。したがって、問題をデバッグして、ロガーメッセージ自体の問題に遭遇することになります。非常に面倒です! +1私の質問 – davidfrancis

+0

@ davidfrancis - ロギングとデバッグは、これが出てきた場所です!乾杯。 – marathon

答えて

4

私は質問で述べたThreadLocalのビット:

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(); 
    } 
} 
} 
+1

これは最低限のオーバーヘッドを伴う安全な賭けです。 –

+0

ローカルのスレッドは静的でなければなりませんか? – davidfrancis

1

最も簡単な方法:これまでに、コレクションやマップの要素にtoString()を呼び出すことはありません。 []を印刷して、それがコレクションまたはマップであることを示し、それを完全に反復することを避けてください。それは、無限の再帰で落ちるのを避けるための唯一の弾丸ではない方法です。

一般的に、あるオブジェクトが別のオブジェクト内のCollectionまたはMapに存在することを予測できず、依存グラフが非常に複雑になり、オブジェクトグラフにサイクルが発生する。

どのIDEを使用していますか?なぜなら、属性がnull以外のコレクションやマップprint []に含まれている要素の数にかかわらず、それが私が使用しているものだからです。

+0

なぜハッシュマップは弾丸ではないのですか? – Aidanc

+2

@Aidancこれは同じ状況です。一般的なケースでは、ハッシュマップの使用先を予測できません。オブジェクトグラフとブームのサイクルの途中で簡単に終わることもあります。 - 無限再帰。 –

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()の契約を尊重しません。

+1

+1これは、オブジェクトがそれ自身の型の別のオブジェクトを参照する場合にのみ機能しますが、多くの場合、人間が多くの人を持つ部門に属するようなものがあります(サイクルを作成します)。解決策はありません –

+0

グラフが異種である場合、減算カウンタを使用して静的再帰関数を作成できます。または同じ方法の複数の特殊化。重要な点は、カウンターを渡してゼロになると失敗することです。 –

+0

シンプルでエレガントで、私の一日を節約しました – Leo

3

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

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

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  
    //...  

} 
3

アイデンティティハッシュセットをとる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}"; 
    } 
} 
+0

グラフにAntiRecusionListタイプが見つかるたびに、これは新しいセットを作成しませんか?したがって、if(見られる)テストは常に新しいセットに対してテストされるので、望ましい効果はありませんか? – marathon

+2

これを避けるには、セットをThreadLocalに配置する必要があります。 – Jorn

+3

私はあなたがアイデンティティをどのようにして作ったかは好きですが。私はそれを盗むだろう:) – marathon

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; 
    } 
} 
+0

...私は彼が正しいと思います、彼はないのですか?私はアイデアの欠陥を見つけることはできません、それは単に動作します。そして、スレッドの配置は簡単です。 –

+0

@Slanec問題は、オブジェクトが以前に見られたときに停止するのではなく、1回の再帰レベルの後に停止するという問題です。それは再帰を防ぐだけで、コレクション内のサイクルを見つけることはできません。 – dcow

+0

@DavidCowden:リストの 'toString'は、displayのために提示されるリストの各項目に対して' toString'を呼び出します。再帰を止めることは、オブジェクトが既に印刷されているためです。これは実際には、OPが達成しようとしているグラフアルゴリズムではありません。ここで問題を理解する方法は少なくとも – Cratylus

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%だけど、私はあなたが私は、Apache CommonsのラングからToStringBuilderを使用することをお勧めアイデア

2

を得ると思います。内部的には、ThreadLocal Mapを使用して「循環オブジェクト参照を検出し、無限ループを回避する」。

関連する問題