防止toString()中无限递归的最有效方法?

如果收集项的图形中的某个地方是对自身的引用,则对集合上的字符串可以进入无限循环。 见下面的例子。

是的,良好的编码实践应该首先防止这种情况,但无论如何,我的问题是:在这种情况下检测递归的最有效方法是什么?

一种方法是在threadlocal中使用一个集合,但这看起来有点沉重。

public class AntiRecusionList extends ArrayList { @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 list1 = new AntiRecusionList(); AntiRecusionList list2 = new AntiRecusionList(); list2.add(list1); list1.add(list2); list1.toString(); //BOOM ! } } 

当我必须迭代风险图时,我通常使用递减计数器来创建函数。

例如 :

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

我不会坚持它,因为你已经知道了,但这并不尊重toString()的合同,它必须简短且可预测。

我在问题中提到的threadlocal位:

 public class AntiRecusionList extends ArrayList { private final ThreadLocal, ?>> fToStringChecker = new ThreadLocal, ?>>() { @Override protected IdentityHashMap, ?> 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(); } } } 

您可以创建带有标识哈希集的toString。

 public String toString() { return toString(Collections.newSetFromMap(new IdentityHashMap())); } private String toString(Set seen) { if (seen.add(this)) { // to string this } else { return "{this}"; } } 

问题不是集合所固有的,它可能发生在具有循环引用的任何对象图形上,例如,双向链表。

我认为一个理智的策略是:如果有可能它是带有循环的对象图的一部分,则类的toString()方法不应该调用其子/的引用的toString() 。 在其他地方,我们可以有一个特殊的方法(可能是静态的,也许是一个辅助类),它产生完整图形的字符串表示。

您可以始终按如下方式跟踪递归(不考虑线程问题):

 public static class AntiRecusionList extends ArrayList { 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; } } 

我建议使用Apache Commons Lang的ToStringBuilder。 在内部,它使用ThreadLocal Map来“检测循环对象引用并避免无限循环”。

最简单的方法:不要在集合或地图的元素上调用toString() 。 只需打印一个[]来表明它是一个集合或地图,并避免完全迭代它。 这是避免无限递归的唯一防弹方法。

在一般情况下,您无法预测在另一个对象内的CollectionMap中将包含哪些元素,并且依赖图可能非常复杂,从而导致在对象图中发生循环的意外情况。

你在用什么IDE? 因为在Eclipse中有一个选项可以在通过代码生成器生成toString()方法时显式处理这种情况 – 这就是我使用的,当属性恰好是非null集合或map print []无论它有多少元素包含的内容。

如果你想要过分,你可以使用一个方法来跟踪嵌套集合,只要你调用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%使用语法,但我认为你明白了

也许你可以在你的toString中创建一个Exception并利用堆栈跟踪知道你在堆栈中的位置,你会发现它有递归调用。 一些框架就是这样做的。

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