Warum überschreiben Arrays in Java nicht equals ()?

8

Ich habe neulich mit einem gearbeitet HashSet, der dies in der Spezifikation geschrieben hat:

[add ()] fügt das angegebene Element e zu dieser Menge hinzu, wenn diese Menge kein Element e2 enthält, so dass (e == null? e2 == null: e.equals (e2))

Ich habe char[]in der verwendet, HashSetbis mir klar wurde, dass es aufgrund dieses Vertrags nicht besser war als ein ArrayList! Da das nicht überschriebene .equals()Array verwendet wird, werden meine Arrays nur auf Referenzgleichheit überprüft, was nicht besonders nützlich ist. Ich weiß, dass Arrays.equals()es das gibt, aber das hilft nicht, wenn man Sammlungen wie verwendet HashSet.

Meine Frage ist also, warum Java-Arrays nicht gleich überschreiben sollten.

Azar
quelle
3
Das Mischen von nativen Arrays und Sammlungen ist nicht wirklich ratsam
Ratschenfreak
1
Es ist das Prinzip der Sache, denke ich. Ein Array ist nach veränderlichen Variablen die zweitprimitivste imperative Datenstruktur. Es handelt sich lediglich um nSpeichersteckplätze, die groß genug sind, um jeweils einen Wert des Tnacheinander geklebten Typs aufzunehmen . Sie sind die Bausteine ​​für anspruchsvollere kurzlebige Sammlungen. Was Sie wollten, war ein List.
Doval
1
@ratchetfreak Ich habe oft Leute das sagen hören, aber nie warum. Warum ist es eine schlechte Idee?
Richard Tingle
@RichardTingle Arrays lassen sich nicht immer gut mit Generika mischen, Arrays sind nicht iterierbar, ihre toString()Darstellung ist größtenteils nutzlos und es gibt fast keinen Vorteil, jemals eines gegenüber einem zu verwenden ArrayList.
Doval
@Doval Aber das sind alles Gründe, überhaupt keine Arrays zu verwenden (dem würde ich in 95% der Fälle zustimmen). Manchmal muss ich Arrays verwenden, weil sie gut mit 3D-Grafiken funktionieren, aber ich möchte ihnen einen Schlüssel zuordnen. So Map<Key, int[]>hat sich immer natürlich angefühlt. Aber ich bin immer nervös, dass etwas Schreckliches auf mich wartet
Richard Tingle

Antworten:

8

In Java musste frühzeitig eine Entwurfsentscheidung getroffen werden:

Sind Arrays primitiv? oder sind sie Objekte?

Die Antwort ist, weder wirklich ... noch beides, wenn man es anders betrachtet. Sie arbeiten ziemlich eng mit dem System selbst und dem Backend des JVM zusammen.

Ein Beispiel hierfür ist die Methode java.lang.System.arraycopy () , für die ein Array eines beliebigen Typs erforderlich ist. Daher muss das Array in der Lage sein, etwas zu erben , und das ist ein Objekt. Und Arraycopy ist eine native Methode.

Arrays sind auch in komisch , dass sie Primitiven halten können ( int, char, double, etc ... , während die anderen Sammlungen nur Objekte halten kann. Schauen Sie zum Beispiel bei java.util.Arrays und die hässliche Seite des Gleichheits Methoden. Dies wurde gestellt in eine als nach Gedanken. deepEquals (Object [], Object []) wurde bis 1,5 , während der Rest der Klasse Arrays in 1.2 hinzugefügt wurde nicht hinzugefügt.

Da es sich bei diesen Objekten um Arrays handelt, können Sie einige Dinge ausführen , die sich im Speicher oder in der Nähe des Speichers befinden - etwas, das Java häufig vor dem Codierer verbirgt. Dies ermöglicht es, bestimmte Dinge schneller zu erledigen, wenn das Objektmodell größtenteils beschädigt wird.

Zu Beginn des Systems gab es einen Kompromiss zwischen Flexibilität und etwas Leistung. Die Leistung siegte und der Mangel an Flexibilität wurde in die verschiedenen Kollektionen eingewickelt. Arrays in Java sind ein dünn implementiertes Objekt über einem primitiven Typ (ursprünglich), der für die Arbeit mit dem System vorgesehen ist, wenn Sie es benötigen.

Zum größten Teil waren rohe Arrays Dinge, die die ursprünglichen Designer anscheinend nur im System zu ignorieren und zu verstecken versuchten. Und sie wollten, dass es schnell geht (frühes Java hatte einige Probleme mit der Geschwindigkeit). Es war eine Warze im Design, dass Arrays keine schönen Arrays sind, aber es war eine, die benötigt wurde, wenn Sie etwas so nah wie möglich am System verfügbar machen wollten. Übrigens haben auch die heutigen Sprachen des frühen Java diese Warze - man kann kein .equals()Array auf C ++ machen.

Java und C ++ haben beide denselben Pfad für Arrays eingeschlagen - eine externe Bibliothek, die die erforderlichen Operationen für Arrays anstelle von Arrays ausführt ... und den Codierern vorschlägt, bessere native Typen zu verwenden, sofern sie nicht wirklich wissen, was sie tun und warum sie es sind mach es so.

Daher ist der Ansatz, .equals in ein Array zu implantieren, falsch, aber es ist der gleiche Fehler, den Codierer aus C ++ kannten. Wählen Sie also das am wenigsten Falsche in Bezug auf die Leistung - lassen Sie es als Implementierung von Object: Zwei Objekte sind genau dann gleich, wenn sie sich auf dasselbe Objekt beziehen.

Das Array muss eine primitive Struktur haben, um mit nativen Bindungen kommunizieren zu können - etwas, das dem klassischen C-Array so nahe wie möglich kommt. Im Gegensatz zu den anderen Grundelementen muss das Array jedoch als Referenz und damit als Objekt übergeben werden können. Es ist also eher ein Primitiv mit einigen Objekt-Hacks an der Seite und einigen Grenzüberprüfungen.


quelle
+1 und danke für die Antwort. Das war , was ich wirklich suchte, die Design - Perspektive hinter der Entscheidung, nicht nur , dass es eine schlechte Idee ist.
Azar
1
@ Azar Es gibt einige Diskussionen bei C2: Java-Arrays sollten erstklassige Objekte sein, die einen Code enthalten, der einige der Hackerangriffe zeigt, die hinter den Kulissen mit einem Array stattfinden ... zusammen mit anderen, die beklagen, dass Arrays keine netten Objekte sind.
2
Glücklicherweise erfordert das Erstellen einer intelligenteren Array-Klasse für C ++ kein Boxen. Java ist nicht so glücklich.
Thomas Eding
3

In Java sind Arrays Pseudoobjekte. Objektreferenzen können Arrays enthalten und verfügen zwar über die Standardobjektmethoden, sind jedoch im Vergleich zu einer echten Sammlung sehr leicht. Arrays tut gerade genug , um den Auftrag eines Objekts und verwenden Sie die Standardimplementierungen von gerecht zu werden equals, hashCodeund toStringganz bewusst.

Betrachten Sie eine Object[]. Ein Element dieses Arrays kann alles sein, was in ein Objekt passt, einschließlich eines anderen Arrays. Es könnte ein primitives Kästchen sein, eine Steckdose, alles. Was bedeutet Gleichheit in diesem Fall? Nun, es hängt davon ab, was sich tatsächlich im Array befindet. Dies ist im allgemeinen Fall, als die Sprache entworfen wurde, nicht bekannt. Gleichheit wird sowohl durch das Array selbst als auch durch seinen Inhalt definiert .

Dies ist der Grund, warum es eine ArraysHilfsklasse gibt, die Methoden zur Berechnung von Gleichheit (einschließlich Deep Equals), Hash-Codes usw. enthält. Diese Methoden sind jedoch in Bezug auf ihre Funktionsweise genau definiert. Wenn Sie unterschiedliche Funktionen benötigen, schreiben Sie Ihre eigene Methode, um zwei Arrays auf Gleichheit zu vergleichen, basierend auf den Anforderungen Ihres Programms.


Obwohl dies keine reine Antwort auf Ihre Frage ist, ist es meiner Meinung nach wichtig zu sagen, dass Sie wirklich Sammlungen anstelle von Arrays verwenden sollten. Konvertieren Sie nur in ein Array, wenn Sie eine Schnittstelle zu einer API herstellen, für die Arrays erforderlich sind. Andernfalls bieten Sammlungen eine bessere Typensicherheit, klarere Verträge und sind im Allgemeinen einfacher zu verwenden als Arrays.


quelle
Arrays sind reale Objekte. Die Tatsache, dass sie der einzige Aggregattyp mit mehreren Elementen sind, bedeutet, dass alle anderen Formulare mit variabler Größe entweder von Arrays oder von O (N) -Objekten außerhalb von sich selbst unterstützt werden müssen, sodass Arrays im Vergleich dazu "leichtgewichtig" sind. Ich denke, das grundlegendere Problem ist nicht, dass Arrays leichtgewichtig sind, sondern dass es so viele Möglichkeiten gibt, sie zu verwenden. Siehe meine Antwort unten.
Supercat
1
"Was bedeutet Gleichheit in diesem Fall?" - ähm, wie wäre es damit, dass die Arrays dieselbe Länge haben und jedes Objekt an jedem Index sowohl in der Quelle als auch im Ziel gemäß dem Vertrag von equals () gleich sein muss? scheint das zu sein, was Sie erwarten würden, und ist der genaue Vertrag, der von Arrays.equals () implementiert wird.
Jeffrey Blattman
@ JeffreyBlattman - Was passiert dann, wenn sich ein Object-Array selbst enthält?
Jules
@JeffreyBlattman nimmt es dann mit den Autoren der Sprache auf, die die Referenzgleichheit für Arrays implementiert haben, aber Arrays.equals()für eine tiefe Gleichheit gesorgt haben .
@Jules "Was passiert dann, wenn sich ein Object-Array selbst enthält?" das gleiche, was passiert, wenn ein Objekt sich selbst enthält. Wenn Sie eine naive Gleichheit implementieren, erhalten Sie einen Stapelüberlauf.
Jeffrey Blattman
1

Die grundlegende Schwierigkeit beim Überschreiben von Arrays equalsbesteht darin, dass eine Variable eines Typs wie int[]auf mindestens drei grundlegend unterschiedliche Arten verwendet werden kann und die Bedeutung von equalsje nach Verwendung variieren sollte. Insbesondere ein Feld vom Typ int[]...

  1. ... kann eine Folge von Werten in einem Array kapseln, die niemals geändert werden, aber frei mit Code geteilt werden können, der sie nicht ändert.

  2. ... kann das ausschließliche Eigentum an einem Container mit ganzzahligen Bestandteilen enthalten, der von seinem Eigentümer nach Belieben mutiert werden kann.

  3. ... kann einen ganzzahligen Container identifizieren, den eine andere Entität verwendet, um ihren Status zu kapseln, und somit als Verbindung zum Status dieser anderen Entität dienen.

Wenn eine Klasse ein HAS - int[]Feld , foodas für eine der ersten zwei Zwecke verwendet wird, dann Fälle xund ysollen betrachten x.foound y.fooals den gleichen Zustand einkapselt , wenn sie dieselbe Folge von Zahlen halten; wenn das Feld für den dritten Zweck verwendet wird, jedoch dann x.foound y.foowürde nur den gleichen Zustand verkapseln , wenn sie identifizieren die gleiche Array [dh sie sind Referenz gleich]. Wenn Java für die drei oben genannten Verwendungen unterschiedliche Typen aufgenommen hätte und equalseinen Parameter verwendet hätte, der angibt, wie die Referenz verwendet wird, wäre es angemessen gewesen int[], die Sequenzgleichheit für die ersten beiden Verwendungen und die Referenzgleichheit für die dritte zu verwenden. Es gibt jedoch keinen solchen Mechanismus.

Beachten Sie auch, dass der int[]Fall die einfachste Art von Array war. Für Arrays, die Verweise auf andere Klassen als Objectoder Array-Typen enthalten, gibt es zusätzliche Möglichkeiten.

  1. Ein Verweis auf ein gemeinsam nutzbares, unveränderliches Array, das Dinge enthält, die sich nie ändern werden.

  2. Ein Verweis auf ein gemeinsam nutzbares, unveränderliches Array, das Dinge identifiziert, die anderen Entitäten gehören.

  3. Ein Verweis auf ein exklusives Array, das Verweise auf Dinge enthält, die sich nie ändern werden.

  4. Ein Verweis auf ein Array, das ausschließlich Eigentum ist und Verweise auf Elemente enthält, die ausschließlich Eigentum sind.

  5. Ein Verweis auf ein ausschließlich im Besitz befindliches Array, das Dinge identifiziert, die anderen Entitäten gehören.

  6. Eine Referenz, die ein Array identifiziert, das einer anderen Entität gehört.

In den Fällen 1, 3 und 4 sollten zwei Array-Referenzen als gleich angesehen werden, wenn die entsprechenden Elemente "gleichwertig" sind. In den Fällen 2 und 5 sollten zwei Array-Referenzen als gleich angesehen werden, wenn sie dieselbe Folge von Objekten identifizieren. In Fall 6 sollten zwei Array-Referenzen nur dann als gleich angesehen werden, wenn sie dasselbe Array identifizieren.

Um equalssich mit Aggregattypen sinnvoll zu verhalten, müssen sie wissen, wie Referenzen verwendet werden. Leider kann das Java-Typsystem dies nicht anzeigen.

Superkatze
quelle
-2

Wenn Sie das Array überschreiben equals()und hashCode()vom Inhalt abhängig sind, ähneln sie Sammlungen - veränderlichen Typen mit nicht konstanten Werten hashCode(). Typen mit Änderungen hashCode()verhalten sich schlecht, wenn sie in Hash-Tabellen und anderen Anwendungen gespeichert werden, die auf hashCode()festen Werten basieren .

Set<List<Integer>> data = new HashSet<List<Integer>>();
List<Integer> datum = new ArrayList<Integer>();
datum.add(1);
data.add(datum);
assert data.contains(datum); // true
datum.add(2);
assert data.contains(datum); // false, WAT???

Arrays hingegen haben einen trivialen hashCode (), können als Hash-Tabellenschlüssel verwendet werden und sind weiterhin veränderbar.

Set<int[]> data = new HashSet<int[]>(67);
int[] datum = new int[]{1, 2};
data.add(datum);
System.out.println(data.contains(datum)); //true
datum[0] = 78;
System.out.println(data.contains(datum)); //true
//PROFIT!!!
Basilevs
quelle
3
Es gibt dort kein Array . Das int[]Typarray.
@ MichaelT, das ist der Punkt.
Basilevs
Warum die Abstimmungen? Der Antwortende hat viel Arbeit geleistet. Ist etwas falsch?
Tom Au
3
@TomAu Die Frage bezieht sich auf das Design des Arrays in Java und seine Pseudo-Objekt-Natur sowie auf die Designentscheidungen, die hinter dieser Auswahl stehen (damit das Array nur referenzielle Gleichheit anstelle einer tiefen Gleichheit verwendet). Diese Antwort versucht andererseits, eine Codelösung für das Umschreiben des Codes zu präsentieren - was in der Frage nicht gestellt wird.
@MichaelT, dies zeigt die identitätsbasierte Kompetenz der Gleichstellungslogik gegenüber der zustandsabhängigen.
Basilevs