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

时间:2012-07-02 19:48:19

标签: java recursion tostring

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

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

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

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

9 个答案:

答案 0 :(得分: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 :(得分: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();
    }
}
}

答案 2 :(得分:3)

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

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

答案 3 :(得分: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}";
   }
}

答案 4 :(得分: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;
   }
}

答案 5 :(得分:2)

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

答案 6 :(得分:1)

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

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

您使用的是什么IDE?因为在Eclipse中有一个选项可以在通过代码生成器生成toString()方法时显式处理这种情况 - 这就是我使用的,当属性恰好是非空集合或地图打印[]时它包含多少元素。

答案 7 :(得分: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%使用语法,但我认为你明白了

答案 8 :(得分:0)

也许你可以在你的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    
    //...    

}