it-swarm.com.de

Soll ein HashSet in Java zu sich selbst hinzugefügt werden dürfen?

Gemäß dem Vertrag für ein Set in Java "darf sich ein Set nicht als Element enthalten" ( source ). Dies ist jedoch bei einem HashSet von Objekten möglich, wie hier gezeigt:

Set<Object> mySet = new HashSet<>();
mySet.add(mySet);
assertThat(mySet.size(), equalTo(1));

Diese Behauptung ist bestanden, aber ich würde erwarten, dass das Verhalten entweder den resultierenden Satz 0 hat oder eine Ausnahme auslöst. Ich erkenne, dass die zugrunde liegende Implementierung eines HashSets eine HashMap ist, aber es scheint, als ob vor dem Hinzufügen eines Elements eine Gleichheitsprüfung durchgeführt werden sollte, um eine Verletzung dieses Vertrags zu vermeiden, nicht wahr?

52
davidmerrick

Andere haben bereits unter Bezugnahme auf Russells Paradoxon darauf hingewiesen, warum es aus mathematischer Sicht fraglich ist.

Dies beantwortet Ihre Frage jedoch nicht auf einer technischen Ebene.

Also lasst uns das analysieren:

Zunächst noch einmal der relevante Teil aus dem JavaDoc der Set Schnittstelle:

Hinweis: Bei Verwendung von veränderlichen Objekten als Set-Elemente ist besondere Vorsicht geboten. Das Verhalten einer Menge wird nicht angegeben, wenn der Wert eines Objekts in einer Weise geändert wird, die sich auf Gleichheitsvergleiche auswirkt, während das Objekt ein Element in der Menge ist. Ein Sonderfall dieses Verbots ist, dass es nicht zulässig ist, dass eine Menge sich selbst als Element enthält.

Interessanterweise macht das JavaDoc der List-Schnittstelle eine ähnliche, wenn auch etwas schwächere und gleichzeitig technischere Aussage:

Es ist zwar zulässig, dass Listen sich selbst als Elemente enthalten, es wird jedoch äußerste Vorsicht geboten: Die Methoden equals und hashCode sind in einer solchen Liste nicht mehr genau definiert.

Und schließlich befindet sich der springende Punkt in JavaDoc der Collection-Schnittstelle , dem gemeinsamen Vorfahren der Set- und der List-Schnittstelle:

Einige Auflistungsoperationen, die eine rekursive Durchquerung der Auflistung durchführen, können mit Ausnahme von selbstreferenziellen Instanzen fehlschlagen wobei sich die Auflistung direkt oder indirekt selbst enthält. Dies schließt die Methoden clone(), equals(), hashCode() und toString() ein. Implementierungen können optional das selbstreferenzielle Szenario behandeln, die meisten aktuellen Implementierungen tun dies jedoch nicht.

(Betonung von mir)

Der kühne Teil gibt einen Hinweis darauf, warum der Ansatz, den Sie in Ihrer Frage vorgeschlagen haben, nicht ausreicht:

es scheint, als sollte vor dem Hinzufügen eines Elements eine Gleichheitsprüfung durchgeführt werden, um einen Verstoß gegen diesen Vertrag zu vermeiden.

Das würde dir hier nicht weiterhelfen. Der entscheidende Punkt ist, dass Sie immer auf Probleme stoßen, wenn die Sammlung direkt oder indirekt ​​sich selbst enthält. Stellen Sie sich dieses Szenario vor:

Set<Object> setA = new HashSet<Object>();
Set<Object> setB = new HashSet<Object>();
setA.add(setB);
setB.add(setA);

Offensichtlich enthält sich keine der Mengen direkt. Aber jeder von ihnen enthält den anderen - und damit sich selbst indirekt. Dies konnte nicht durch eine einfache referentielle Gleichheitsprüfung (mit == in der add Methode).


Das Vermeiden eines solchen "inkonsistenten Zustands" ist in der Praxis grundsätzlich unmöglich. Natürlich ist es theoretisch möglich, referentielle Erreichbarkeit Berechnungen zu verwenden. Tatsächlich muss der Garbage Collector genau das tun!

Aber es wird unmöglich in der Praxis, wenn benutzerdefinierte Klassen beteiligt sind. Stellen Sie sich eine Klasse wie diese vor:

class Container {

    Set<Object> set;

    @Override 
    int hashCode() {
        return set.hashCode(); 
    }
}

Und herumspielen mit diesem und seinem set:

Set<Object> set = new HashSet<Object>();
Container container = new Container();
container.set = set;
set.add(container);

Die add -Methode von Set kann grundsätzlich nicht erkennen, ob das dort hinzugefügte Objekt some (indirekt) auf die Menge selbst verweist.

Um es kurz zu machen:

Sie können den Programmierer nicht daran hindern, Dinge durcheinander zu bringen.

52
Marco13

Das Hinzufügen der Sammlung zu sich selbst einmal bewirkt, dass der Test bestanden wird. Das Hinzufügen von zweimal führt zu dem StackOverflowError, nach dem Sie gesucht haben.

Aus Sicht eines persönlichen Entwicklers ist es nicht sinnvoll, eine Überprüfung des zugrunde liegenden Codes zu erzwingen, um dies zu verhindern. Die Tatsache, dass Sie ein StackOverflowError in Ihrem Code erhalten, wenn Sie zu oft versuchen, dies zu tun, oder das hashCode berechnen, was einen sofortigen Überlauf verursachen würde, sollte ausreichen, um sicherzustellen, dass kein Verstand vorliegt Entwickler würden diese Art von Code in ihrer Codebasis behalten.

22
Makoto

Sie müssen das vollständige Dokument lesen und es vollständig zitieren:

Das Verhalten einer Menge wird nicht angegeben wenn der Wert eines Objekts in einer Weise geändert wird, die sich auf Gleichheitsvergleiche auswirkt, während das Objekt ein Element in der Menge ist. Ein Sonderfall dieses Verbots ist, dass es nicht zulässig ist, dass eine Menge sich selbst als Element enthält.

Die eigentliche Einschränkung steht im ersten Satz. Das Verhalten ist nicht spezifiziert , wenn ein Element einer Menge mutiert ist.

Da das Hinzufügen einer Menge zu sich selbst sie mutiert und das erneute Hinzufügen sie erneut mutiert, ist das Ergebnis nicht angegeben.

Beachten Sie, dass die Einschränkung darin besteht, dass das Verhalten nicht spezifiziert ist und dass ein Sonderfall dieser Einschränkung fügt die Menge zu sich selbst hinzu.

Der Doc sagt mit anderen Worten, dass das Hinzufügen eines Sets zu sich selbst zu einem nicht spezifizierten Verhalten führt, wie Sie es sehen. Es liegt an der konkreten Implementierung, sich damit zu befassen (oder nicht).

12
Polygnome

Ich stimme Ihnen zu, dass dieses Verhalten aus mathematischer Sicht wirklich keinen Sinn ergibt.

Hier gibt es zwei interessante Fragen: Inwieweit haben die Designer der Schnittstelle Set versucht, eine mathematische Menge zu implementieren? Zweitens, auch wenn sienichtwaren, inwieweit befreit sie das von den Regeln der Mengenlehre?

Bei der ersten Frage werde ich Sie auf die Dokumentation des Sets verweisen:

Eine Sammlung, die keine doppelten Elemente enthält. Genauer gesagt enthalten Mengen kein Elementpaar e1 und e2, so dass e1.equals (e2) und höchstens ein Nullelement sind. Wie der Name andeutet, modelliert diese Schnittstelle die mathematische Mengenabstraktion.

Erwähnenswert ist hier, dass aktuelle Formulierungen der Mengenlehre es nicht zulassen, dass Mengen Mitglieder ihrer selbst sind. (Siehe das Axiom der Regelmäßigkeit ). Dies ist zum Teil auf Russells Paradoxon zurückzuführen, das einen Widerspruch in der naiven Mengenlehre aufgedeckt hat (was erlaubte, dass eine MengeeineSammlung von Objekten ist - Es gab kein Verbot, Sets einzuschließen. Dies wird häufig durch das Barber Paradox veranschaulicht: Nehmen wir an, dass in einer bestimmten Stadt ein Friseur alle Männer rasiert - undnurdie Männer - die das tun sich nicht rasieren. Frage: rasiert sich der Friseur selbst? Wenn er es tut, verletzt es die zweite Einschränkung; Wenn er dies nicht tut, verstößt es gegen die erste Bedingung. Dies ist natürlich logisch unmöglich, aber nach den Regeln der naiven Mengenlehre durchaus zulässig (weshalb die neuere "Standard" -Formulierung der Mengenlehre Mengen ausdrücklich verbietet, sich selbst zu enthalten).

In dieser Frage zu Math.SE wird mehr darüber diskutiert, warum Mengen kein Element ihrer selbst sein können.

Vor diesem Hintergrund wirft dies die zweite Frage auf: Selbst wenn die Designernichtexplizit versucht hätten, eine mathematische Menge zu modellieren, wäre dies völlig "ausgenommen" von der Probleme im Zusammenhang mit der naiven Mengenlehre? Ich denke nicht - ich denke, dass viele der Probleme, die die Theorie der naiven Mengen plagten,jedeArt einer Sammlung plagen würden, die in einer Weise, die der Theorie der naiven Mengen entspricht, nicht ausreichend eingeschränkt war . Ich lese vielleicht zu viel darüber, aber der erste Teil der Definition von Set in der Dokumentation klingt verdächtig wie das intuitive Konzept einer Menge in der naiven Mengenlehre:

Eine Sammlung, die keine doppelten Elemente enthält.

Zugegeben (und zu ihrem Vorteil), sie stellen mindestenseinigeEinschränkungen auf dieses später (einschließlich der Feststellung, dass Sie wirklich nicht versuchen sollten, ein Set selbst enthalten zu lassen), aber man könnte sich fragen, ob es wirklich "genug" ist, um die Probleme mit der naiven Mengenlehre zu vermeiden. Aus diesem Grund haben Sie beispielsweise das Problem "Schildkröten runter", wenn Sie versuchen, den Hash-Code eines HashSets zu berechnen, das sich selbst enthält. Dies ist nicht, wie einige andere vorgeschlagen haben, nur ein praktisches Problem - es ist eine Veranschaulichung der grundlegenden theoretischen Probleme bei dieser Art von Formulierung.

Als kurzen Exkurs erkenne ich natürlich, dass es einige Einschränkungen gibt, wie genau eine Sammlungsklasse eine mathematische Menge wirklich modellieren kann. Zum Beispiel warnt Javas Dokumentation vor den Gefahren, veränderbare Objekte in ein Set aufzunehmen. Einige andere Sprachen, wie Python, versuchen zumindest, viele Arten von veränderlichen Objekten vollständig zu verbannen :

Die gesetzten Klassen werden mit Hilfe von Wörterbüchern implementiert. Dementsprechend sind die Anforderungen für Mengenelemente dieselben wie für Wörterbuchschlüssel. nämlich, dass das Element sowohl __eq__() als auch __hash__() definiert. Daher können Mengen keine veränderlichen Elemente wie Listen oder Wörterbücher enthalten. Sie können jedoch unveränderliche Sammlungen wie Tupel oder Instanzen von ImmutableSet enthalten. Zur Vereinfachung der Implementierung von Mengen von Mengen werden innere Mengen automatisch in unveränderliche Form konvertiert, z. B. wird Set([Set(['dog'])]) in Set([ImmutableSet(['dog'])]) transformiert.

Zwei weitere wichtige Unterschiede, auf die andere hingewiesen haben, sind:

  • Java-Sets sind veränderbar
  • Java-Mengen sind endlich. Offensichtlich gilt dies fürjedeErfassungsklasse: Abgesehen von Bedenken hinsichtlich der tatsächlichen Unendlichkeit verfügen Computer nur über eine begrenzte Menge an Speicher. (Einige Sprachen, wie Haskell, haben faul unendlich viele Datenstrukturen. Meiner Meinung nach scheint eine gesetzmäßige Auswahlfolge jedoch ein natürlicheres Modell für diese zu sein als die klassische Mengenlehre, aber das ist nur meine Meinung.).

TL; DR Nein, das sollte wirklich nicht erlaubt sein (oder zumindest sollte man das niemals tun), da Sets nicht Mitglieder von sein können sich.

8
EJoshuaS