簡體   English   中英

為什么Java toString()在間接循環中無限循環?

[英]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對象。

鑒於它受此方法的限制,這里有一個檢查它的算法:

  • root是調用第一個toString()的對象(為了確定這一點,你需要保持關於toString當前是否正在進行的狀態;所以這很不方便)。
    • 在遍歷每個對象時,將其添加到IdentityHashMap,以及唯一標識符(例如遞增的索引)。
    • 但如果此對象已在Map中,請寫出其標識符。

此方法還可以正確呈現multirefs(一個多次引用的節點)。

內存成本是IdentityHashMap(每個對象一個引用和索引); 復雜性成本是有向圖中每個節點(即每個打印的對象)的哈希查找。

我認為從根本上說這是因為雖然語言試圖阻止你在腳下射擊自己,但它不應該以一種昂貴的方式這樣做。 因此,雖然幾乎可以自由地比較對象指針(例如obj == this ),但除此之外的任何事情都涉及在您傳入的對象上調用方法。

在這一點上,庫代碼對你傳入的對象一無所知。例如,泛型實現不知道它們是否是Collection (或Iterable )本身的實例,而它可以找到這個通過instanceof ,誰來說是否是一個“類似集合”的對象實際上不是一個集合,但仍然包含一個延遲的循環引用? 其次,即使它是一個集合,也不知道它的實際實現是什么,因此行為就像。 理論上,人們可以擁有一個包含所有Longs的集合,這些集合將被懶惰地使用; 但由於圖書館不知道這一點,因此迭代每個條目會非常昂貴。 或者實際上甚至可以設計一個永遠不會終止的迭代器集合(雖然這在實踐中很難使用,因為很多構造/庫類假設hasNext最終會返回false )。

所以它基本上歸結為一個未知的,可能是無限的成本,以阻止你做一些可能實際上不是問題的事情。

我只想指出這句話:

當使用toString()進行打印時, Java將檢測集合中的直接循環

是誤導。

Java (JVM,語言本身等)沒有檢測到自引用。 相反,這是java.util.AbstractCollectiontoString()方法/覆蓋的屬性。

如果您要創建自己的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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM