这更像是我想分享的问题而不是问题:当使用toString()
进行打印时,Java将检测Collection中的直接循环(Collection指向自身),但不是间接循环(其中Collection是指另一个集合,它指的是第一个 - 或者更多的步骤)。
import java.util.*;
public class ShonkyCycle {
static public void main(String[] args) {
List a = new LinkedList();
a.add(a); // direct cycle
System.out.println(a); // works: [(this Collection)]
List b = new LinkedList();
a.add(b);
b.add(a); // indirect cycle
System.out.println(a); // shonky: causes infinite loop!
}
}
对我来说这是一个真正的问题,因为它发生在调试代码中以打印出Collection(当它遇到直接循环时我感到很惊讶,所以我错误地认为他们已经实现了一般的检查)。有一个问题:为什么?
我能想到的解释是,检查引用自身的集合是非常便宜的,因为您只需要存储集合(您已经拥有),但是对于更长的周期,您需要存储所有您遇到的集合,从根开始。此外,您可能无法确定 根是什么,因此您必须将每个集合存储在系统中 - 无论如何都要存储 - 但您还必须这样做每个集合元素的哈希查找。对于相对罕见的周期(在大多数编程中),这是非常昂贵的。 (我认为)它检查直接周期的唯一原因是因为它如此便宜(一个参考比较)。
好的...我有点回答了我自己的问题 - 但是我错过了什么重要的事吗?有人想要添加任何内容吗?澄清:我现在意识到我看到的问题特定于打印一个集合(即toString()
方法)。循环本身没有问题(我自己使用它们并需要它们);问题是Java无法打印它们。 编辑 Andrzej Doyle指出它不仅仅是集合,而是任何调用toString
的对象。
鉴于它受此方法的限制,这里有一个检查它的算法:
toString()
的对象(为了确定这一点,你需要保持关于toString当前是否正在进行的状态;所以这很不方便)。
此方法还可以正确呈现multirefs(多次引用的节点)。
内存成本是IdentityHashMap(每个对象一个引用和索引);复杂性成本是有向图中每个节点(即每个打印的对象)的哈希查找。
答案 0 :(得分:5)
我认为从根本上说这是因为虽然语言试图阻止你在脚下射击,但它不应该以一种昂贵的方式这样做。因此,虽然比较对象指针(例如obj == this
)几乎是免费的,但除此之外的任何事情都涉及调用您传入的对象上的方法。
此时,库代码对您传入的对象一无所知。例如,泛型实现不知道它们是Collection
的实例(或{{1 }})本身,虽然它可以通过Iterable
找到它,谁会说它是否是一个“类似集合”的对象,它实际上不是一个集合,但仍然包含一个延迟的循环引用?其次,即使它是一个集合,也不知道它的实际实现是什么,因此行为就像。理论上,人们可以拥有一个包含所有Longs的集合,这些集合将被懒惰地使用;但由于图书馆不知道这一点,因此迭代每个条目会非常昂贵。或者实际上甚至可以设计一个永远不会终止的迭代器集合(虽然这在实践中很难使用,因为很多构造/库类假设instanceof
最终会返回hasNext
)。 / p>
所以它基本上归结为一个未知的,可能是无限的代价,以阻止你做一些可能实际上不是问题的事情。
答案 1 :(得分:3)
我只想指出这句话:
使用toString()打印时, Java将在集合中检测直接循环
具有误导性。
Java (JVM,语言本身等)未检测到自引用。相反,这是toString()
方法/覆盖java.util.AbstractCollection
的属性。
如果您要创建自己的Collection
实现,语言/平台不会自动保护您免受这样的自我引用 - 除非您扩展AbstractCollection
,否则您必须确保你自己掩盖了这个逻辑。
我可能会在这里分裂,但我认为这是一个重要的区别。仅仅因为JDK中的一个基础类做了某些事情并不意味着“Java”作为一个整体的保护伞。
以下是AbstractCollection.toString()
中的相关源代码,其中的关键行已注明:
public String toString() {
Iterator<E> i = iterator();
if (! i.hasNext())
return "[]";
StringBuilder sb = new StringBuilder();
sb.append('[');
for (;;) {
E e = i.next();
// self-reference check:
sb.append(e == this ? "(this Collection)" : e);
if (! i.hasNext())
return sb.append(']').toString();
sb.append(", ");
}
}
答案 2 :(得分:1)
您建议的算法问题是您需要将IdentityHashMap传递给所涉及的所有集合。使用已发布的Collection API无法做到这一点。 Collection接口未定义toString(IdentityHashMap)
方法。
我想,无论是谁,Sun都会将自我参考检查纳入考虑所有这一切的AbstractCollection.toString()
方法中,并且(与他的同事一起)决定“整体解决方案”超越顶部。我认为目前的设计/实施是正确的。
并不要求Object.toString实现是防弹的。
答案 3 :(得分:0)
你是对的,你已经回答了自己的问题。检查更长的周期(特别是长周期,例如周期长度1000)将是过多的开销,并且在大多数情况下不需要。如果有人想要,他必须亲自检查。
然而,直接循环的情况很容易检查并且会更频繁地发生,所以它是由Java完成的。
答案 4 :(得分:0)
你无法真正发现间接周期;这是暂停问题的一个典型例子。