[英]Why does Java toString() loop infinitely on indirect cycles?
這更像是一個我想分享的問題而不是一個問題:當使用toString()
打印時,Java將檢測Collection中的直接循環(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(當它遇到直接循環時我感到很驚訝,所以我認為他們已經錯誤地實現了一般的檢查)。 有一個問題:為什么?
我能想到的解釋是,檢查一個引用自身的集合是非常便宜的,因為你只需要存儲集合(你已經存在),但是對於更長的周期,你需要存儲所有的集合你遭遇,從根開始。 此外,您可能無法肯定地告訴的根源是什么,所以你必須存儲系統中的每個集合-你無論如何做-但你也不得不做的每集合元素的哈希查找。 對於相對罕見的周期(在大多數編程中),這是非常昂貴的。 (我認為)它檢查直接循環的唯一原因是因為它如此便宜(一個參考比較)。
好的...我有點回答了我自己的問題 - 但是我錯過了什么重要的事嗎? 有人想要添加任何東西嗎?
澄清:我現在意識到我看到的問題特定於打印 Collection(即toString()
方法)。 循環本身沒有問題(我自己使用它們並需要它們); 問題是Java無法打印它們。 編輯 Andrzej Doyle指出它不僅僅是集合,而是任何調用toString
對象。
鑒於它受此方法的限制,這里有一個檢查它的算法:
toString()
的對象(為了確定這一點,你需要保持關於toString當前是否正在進行的狀態;所以這很不方便)。
此方法還可以正確呈現multirefs(一個多次引用的節點)。
內存成本是IdentityHashMap(每個對象一個引用和索引); 復雜性成本是有向圖中每個節點(即每個打印的對象)的哈希查找。
我認為從根本上說這是因為雖然語言試圖阻止你在腳下射擊自己,但它不應該以一種昂貴的方式這樣做。 因此,雖然幾乎可以自由地比較對象指針(例如obj == this
),但除此之外的任何事情都涉及在您傳入的對象上調用方法。
在這一點上,庫代碼對你傳入的對象一無所知。例如,泛型實現不知道它們是否是Collection
(或Iterable
)本身的實例,而它可以找到這個通過instanceof
,誰來說是否是一個“類似集合”的對象實際上不是一個集合,但仍然包含一個延遲的循環引用? 其次,即使它是一個集合,也不知道它的實際實現是什么,因此行為就像。 理論上,人們可以擁有一個包含所有Longs的集合,這些集合將被懶惰地使用; 但由於圖書館不知道這一點,因此迭代每個條目會非常昂貴。 或者實際上甚至可以設計一個永遠不會終止的迭代器集合(雖然這在實踐中很難使用,因為很多構造/庫類假設hasNext
最終會返回false
)。
所以它基本上歸結為一個未知的,可能是無限的成本,以阻止你做一些可能實際上不是問題的事情。
我只想指出這句話:
當使用toString()進行打印時, Java將檢測集合中的直接循環
是誤導。
Java (JVM,語言本身等)沒有檢測到自引用。 相反,這是java.util.AbstractCollection
的toString()
方法/覆蓋的屬性。
如果您要創建自己的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(", ");
}
}
您建議的算法的問題是您需要將IdentityHashMap傳遞給所涉及的所有集合。 使用已發布的Collection API無法做到這一點。 Collection接口未定義toString(IdentityHashMap)
方法。
我想,無論是誰,Sun都會將自引用檢查放入AbstractCollection.toString()
方法中,並且(與他的同事一起)認為“整體解決方案”是最重要的。 我認為目前的設計/實施是正確的。
並不要求Object.toString實現是防彈的。
你是對的,你已經回答了自己的問題。 檢查更長的周期(特別是長周期,例如周期長度1000)將是過多的開銷,並且在大多數情況下不需要。 如果有人想要,他必須親自檢查。
然而,直接循環的情況很容易檢查並且會更頻繁地發生,所以它是由Java完成的。
你無法真正發現間接周期; 這是暫停問題的典型例子。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.