collection 的 toString() 方法中的 StackOverflowError 是错误吗?

Is StackOverflowError at a collection's toString() method a bug?

我写了这段代码来演示:

List<Object> list1 = new ArrayList<>();
List<Object> list2 = new ArrayList<>();
list2.add(list1);
list1.add(list2);
list1.toString();

此代码将导致 WhosebugError

不过我知道 java 的 collections 已经做了一些努力来防止这种情况发生,例如这段代码可以正常工作:

List<Object> list1 = new ArrayList<>();
list1.add(list1);
list1.toString();

其他一些语言似乎也能处理它(两种情况)。第一个例子没有“消毒”而第二个例子是有原因的吗?这是一个错误吗?

这不完全是一个错误 - 它更像是一种不幸的实用主义。 toString 应该尽最大努力不抛出任何东西(它是一个调试工具,这会很烦人——在 toString 计算期间没有真正需要报告无关的情况)——但它应该尝试的努力程度是有限的,因为 toString 也应该是高性能的,并且易于编写。

问题是,java是OO语言,东西是封装的。仅通过查看组件是什么,就无法知道在 'component' 对象上调用 toString 最终会再次对自己调用 toString 。毕竟它只是一个对象(列表可以包含任何东西——包括其他列表、集合、地图等)。 Java,与其他一些语言不同,它还具有完全可扩展的核心数据类型:您可以编写自己的列表实现,很多人都这样做。 (例如,java.util.concurrent 有一堆非常有用的核心集合 API 实现)。因此,试图通过集合之间的某种内部通信系统来迎合这种情况会将管理所有这些的责任也推给那些扩展的人,这不是务实的选择。

有一些非常棘手的方法,例如尝试捕获 Whosebug,或者在 ThreadLocal 中设置一个标志并在该标志为真时返回替代值(因为这意味着对某些组件对象调用 toString 最终结束了再次对你调用 toString),但现在你必须说明 toString 计算不能将工作外包给其他线程。任何人都不太可能这样做,但是向 toString 实现添加一大堆奇怪的警告,或者将其变成一个小框架来传达递归组件的概念,这些都是完全不适合调试工具的事情:使其紧密束缚,复杂。