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

问题描述

我写了这段代码来演示:

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

代码将导致 StackOverflowError

但是我知道在java的集合中有一些努力来防止这种情况,例如这段代码可以正常工作:

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

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

解决方法

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

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

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