您不能创建一种避免引用循环的编程语言,因为创建循环是应用程序程序员的责任。您只能创建一种要求程序员始终承担该责任的语言。
这是允许或不允许循环的数据结构的基本设计。例如。在 Java 中,List 是引用列表,因此,直接或间接存储 List 本身没有问题。但是举一个更直接的例子,对于双向链表,每个节点都有一个指向它的下一个节点和它的前一个节点的指针。这已经足以形成参考循环:
┌──────┐ ┌──────┐ ┌──────┐ ┌──────┐
│ │ -next-----> │ │ -next-----> │ │ -next-----> │ │
│ Node │ │ Node │ │ Node │ │ Node │
│ │ <-previous- │ │ <-previous- │ │ <-previous- │ │
└──────┘ └──────┘ └──────┘ └──────┘
这已经形成了多个循环,两个相邻节点之间通过它们的previous 和next 引用形成短循环,但也在其他节点之间间接形成。
要通过弱引用切断这些循环,Node 类的设计者必须决定是使 next 还是 previous 引用成为弱引用。它们中的任何一个都会破坏其中一个基本功能:
- 如果您有对第一个节点的引用,则可以通过
next 引用链到达并遍历所有节点
- 如果您有对最后一个节点的引用,则可以通过
previous 引用链到达并遍历所有节点
- 事实上,对任何个链接节点的引用足以到达所有节点
- 如果所有节点都无法访问,则所有节点都可以进行垃圾回收
如果您声明两个引用之一为弱引用,则不能假设对一个节点的引用使所有节点都保持活动状态。如果next 很弱,您需要始终保留对最后一个节点的引用,以防止突然删除下一个节点。如果previous 很弱,则必须始终保持对第一个节点的引用。
因此,要求开发人员始终通过弱引用切断循环会导致对方式的基本限制,必须设计软件。再举一个例子,考虑一个注册了监听器的组件,当事件发生时会修改该组件(或另一个组件引用前者),从而形成一个循环循环。使侦听器引用变弱意味着它可能会无故突然消失。
也就是说,即使是弱引用本身也是垃圾收集器在进行图遍历时自然提供的一个特性,因为只有那些在发现它们的存在时才能廉价地采取行动。当一个引用计数系统发现最后一个/唯一存在的强引用已被删除时,它可以轻松地释放一个对象,但它需要额外记录所有现有的弱引用,以便在对象被删除时清除它们释放。
这就是引用计数不再有意义的地方。实现起来不再简单(这是唯一的优点),同时效率低下,因为在遍历对象图时,比如遍历链表,你必须永久更新引用计数器(在一个线程中)如果您的语言支持多线程,这是一种安全的方式),而遍历垃圾收集器仅在必须检查可回收内存时才需要做一些事情。而且它只需要遍历活着的对象,因此它可以无所事事的时间越长,它在下一个循环中的工作就越少。