Это больше вопрос, который я хотел бы поделиться, чем вопрос: при печати с toString()
Java будет обнаруживать прямые циклы в коллекции (где коллекция относится к себе), но не косвенные циклы (где коллекция относится к другая коллекция, которая относится к первому - или с большим количеством шагов).
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!
}
}
Это было для меня настоящим уловкой, потому что это происходило при отладке кода для распечатки Коллекции (я был удивлен, когда он перехватил прямой цикл, поэтому я ошибочно предположил, что они реализовали проверку в целом). Возникает вопрос: почему?
Объяснение, которое я могу придумать, состоит в том, что проверять коллекцию, которая ссылается на себя, очень недорого, поскольку вам нужно только хранить коллекцию (которая у вас уже есть), но для более длительных циклов вам нужно хранить все коллекции, с которыми вы сталкиваетесь, начиная с корня. Кроме того, вы не сможете точно сказать, что такое корень , и поэтому вам придется хранить каждую коллекцию в системе - что вы делаете в любом случае - но вам также придется сделать поиск хеша на каждом элементе коллекции. Это очень дорого для относительно редкого случая циклов (в большинстве программ). (Я думаю) единственная причина, по которой он проверяет прямые циклы, в том, что он такой дешевый (одно эталонное сравнение).
ОК ... Я вроде ответил на свой вопрос - но я что-то упустил? Кто-нибудь хочет что-нибудь добавить?
Разъяснение: теперь я понимаю, что проблема, с которой я столкнулся, относится только к печати a Collection (то есть к методу toString()
). Там нет проблем с циклами per se (я использую их сам и мне нужно иметь их); проблема в том, что Java не может их печатать. Редактировать Анджей Дойл отмечает, что это не просто коллекции, но любой объект, чей toString
называется.
Учитывая, что он ограничен этим методом, вот алгоритм для его проверки:
- корень - это объект, для которого вызывается первый
toString()
(чтобы определить это, вам нужно сохранить состояние о том, выполняется ли toString в данный момент или нет; так что это неудобно).
- при прохождении каждого объекта вы добавляете его в IdentityHashMap вместе с уникальным идентификатором (например, увеличенный индекс).
- но если этот объект уже находится на карте, вместо этого запишите его идентификатор.
Этот подход также правильно отображает multirefs (узел, на который ссылаются более одного раза).
Стоимость памяти - IdentityHashMap (одна ссылка и индекс на объект); стоимость сложности - это поиск хеша для каждого узла в ориентированном графе (то есть для каждого печатаемого объекта).