Ich habe eine Weile darüber nachgedacht, warum Java und C # (und ich bin sicher, andere Sprachen) standardmäßig auf Gleichheit verweisen ==
.
Bei der Programmierung, die ich mache (was sicherlich nur eine kleine Auswahl von Programmierproblemen ist), möchte ich fast immer logische Gleichheit beim Vergleichen von Objekten anstelle von Referenzgleichheit. Ich habe versucht zu überlegen, warum diese beiden Sprachen diesen Weg gegangen sind, anstatt ihn umzukehren und ==
logische Gleichheit zu haben und .ReferenceEquals()
als Referenzgleichheit zu verwenden.
Die Verwendung von Referenzgleichheit ist offensichtlich sehr einfach zu implementieren und führt zu einem sehr konsistenten Verhalten, aber es scheint nicht gut zu den meisten Programmierpraktiken zu passen, die ich heute sehe.
Ich möchte die Probleme bei der Implementierung eines logischen Vergleichs nicht ignorieren, und dass sie in jeder Klasse implementiert werden müssen. Mir ist auch klar, dass diese Sprachen vor langer Zeit entworfen wurden, aber die allgemeine Frage bleibt.
Gibt es einen großen Vorteil des Standardwerts, den ich einfach vermisse, oder erscheint es vernünftig, dass das Standardverhalten logische Gleichheit sein sollte, und dass der Standardwert auf Referenzgleichheit zurückgesetzt wird, wenn es für die Klasse keine logische Gleichheit gibt?
quelle
Equals()
das Verhalten von nicht automatisch ändert , wenn Sie es überschreiben==
?Antworten:
C # macht es, weil Java es tat. Java hat dies getan, weil Java das Überladen von Operatoren nicht unterstützt. Da die Wertgleichheit für jede Klasse neu definiert werden muss, kann es sich nicht um einen Operator handeln, sondern muss eine Methode sein. IMO war dies eine schlechte Entscheidung. Es ist viel einfacher zu schreiben und zu lesen
a == b
alsa.equals(b)
und viel natürlicher für Programmierer mit C- oder C ++ - Erfahrung, aber esa == b
ist fast immer falsch. Bugs aus der Verwendung von==
wo.equals
erforderlich war , hat unzählige Tausende von Programmierern Stunden verschwendet.quelle
==
für viele Klassen überladen und vor einigen Monaten entdeckte ich, dass einige Entwickler nicht wussten, was sie==
tatsächlich taten. Dieses Risiko besteht immer dann, wenn die Semantik eines Konstrukts nicht offensichtlich ist. Dieequals()
Notation sagt mir, dass ich eine benutzerdefinierte Methode verwende und dass ich sie irgendwo nachschlagen muss. Fazit: Ich denke, das Überladen von Operatoren ist im Allgemeinen ein offenes Thema.+
zum Beispiel, die zusätzlich tut (numerischer Werte) und Verkettung von Strings zur gleichen Zeit.a == b
es für Programmierer mit C-Erfahrung natürlicher sein, da C das Überladen benutzerdefinierter Operatoren nicht unterstützt? (Zum Beispiel ist die C-Methode zum Vergleichen von Zeichenfolgenstrcmp(a, b) == 0
nichta == b
.)char *
. Mir scheint klar, dass der Vergleich von zwei Zeigern auf Gleichheit nicht mit einem Zeichenfolgenvergleich identisch ist.Die kurze Antwort: Konsistenz
Um Ihre Frage jedoch richtig zu beantworten, schlage ich vor, dass wir einen Schritt zurückgehen und uns mit der Frage befassen, was Gleichheit in einer Programmiersprache bedeutet . Es gibt mindestens DREI verschiedene Möglichkeiten, die in verschiedenen Sprachen verwendet werden:
Diese drei Gleichheitstypen werden häufig verwendet, weil sie einfach zu implementieren sind: Alle drei Gleichheitsprüfungen können leicht von einem Compiler generiert werden (im Fall einer tiefen Gleichheit muss der Compiler möglicherweise Tag-Bits verwenden, um Endlosschleifen zu verhindern, wenn eine Struktur dies erfordert) verglichen werden hat zirkuläre Verweise). Es gibt aber noch ein anderes Problem: Keines davon könnte angemessen sein.
In nicht-trivialen Systemen wird Gleichheit von Objekten oft als etwas zwischen tiefer Gleichheit und Referenzgleichheit definiert. Um zu überprüfen, ob wir zwei Objekte in einem bestimmten Kontext als gleich betrachten möchten, müssen möglicherweise einige Attribute mit denen verglichen werden, die sich im Gedächtnis befinden, und andere Attribute müssen mit einer tiefen Gleichheit verglichen werden, während einige Attribute möglicherweise etwas ganz anderes sein dürfen. Was wir wirklich wollen, ist eine „vierte Art von Gleichheit“, eine wirklich schöne, die in der Literatur oft als semantische Gleichheit bezeichnet wird . In unserem Bereich sind die Dinge gleich, wenn sie gleich sind. =)
So können wir auf Ihre Frage zurückkommen:
Was meinen wir, wenn wir 'a == b' in einer beliebigen Sprache schreiben? Im Idealfall sollte es immer dasselbe sein: Semantische Gleichheit. Das ist aber nicht möglich.
Eine der wichtigsten Überlegungen ist, dass wir zumindest für einfache Typen wie Zahlen erwarten, dass zwei Variablen nach der Zuweisung desselben Werts gleich sind. Siehe unten:
In diesem Fall erwarten wir, dass 'a gleich b' in beiden Anweisungen ist. Alles andere wäre verrückt. Die meisten (wenn nicht alle) Sprachen folgen dieser Konvention. Daher wissen wir mit einfachen Typen (auch als Werte bezeichnet), wie man eine semantische Gleichheit erreicht. Bei Objekten kann das etwas ganz anderes sein. Siehe unten:
Wir erwarten, dass das erste "Wenn" immer wahr sein wird. Aber was erwarten Sie beim zweiten "Wenn"? Es kommt wirklich darauf an. Kann 'DoSomething' die (semantische) Gleichheit von a und b verändern?
Das Problem mit der semantischen Gleichheit besteht darin, dass sie vom Compiler nicht automatisch für Objekte generiert werden kann und sich auch nicht aus den Zuweisungen ergibt . Für den Benutzer muss ein Mechanismus bereitgestellt werden, um die semantische Gleichheit zu definieren. In objektorientierten Sprachen ist dieser Mechanismus eine vererbte Methode: equals . Wenn wir einen Teil des OO-Codes lesen, erwarten wir nicht, dass eine Methode in allen Klassen exakt dieselbe Implementierung hat. Wir sind an Vererbung und Überlastung gewöhnt.
Bei Operatoren erwarten wir jedoch dasselbe Verhalten. Wenn Sie 'a == b' sehen, sollten Sie in allen Situationen die gleiche Art von Gleichheit (von den 4 oben) erwarten. Aus Gründen der Konsistenz verwendeten die Sprachdesigner die Referenzgleichheit für alle Typen. Es sollte nicht davon abhängen, ob ein Programmierer eine Methode überschrieben hat oder nicht.
PS: Die Sprache Dee unterscheidet sich geringfügig von Java und C #: Der Gleichheitsoperator bedeutet flache Gleichheit für einfache Typen und semantische Gleichheit für benutzerdefinierte Klassen (wobei die Verantwortung für die Implementierung der = -Operation beim Benutzer liegt - es wird keine Standardeinstellung bereitgestellt). Da für einfache Typen flache Gleichheit immer semantische Gleichheit ist, ist die Sprache konsistent. Der Preis, den es zahlt, ist, dass der Gleichheitsoperator für benutzerdefinierte Typen standardmäßig undefiniert ist. Sie müssen es implementieren. Und manchmal ist das einfach langweilig.
quelle
When you see ‘a == b’ you should expect the same type of equality (from the 4 above) in all situations.
Die Sprachdesigner von Java verwendeten die Referenzgleichheit für Objekte und die semantische Gleichheit für Primitive. Mir ist nicht klar, dass dies die richtige Entscheidung war oder dass diese Entscheidung "konsistenter" ist, als es zuzulassen==
, dass sie für semantische Gleichheit von Objekten überladen wird.a
undb
vom gleichen Typ ist , der Ausdrucka==b
überprüft , oba
undb
halten Sie die gleiche Sache. Wenn einer von ihnen einen Verweis auf Objekt Nr. 291 und der andere einen Verweis auf Objekt Nr. 572 enthält, ist dies nicht der Fall. Der Inhalt von Objekt Nr. 291 und Nr. 572 kann äquivalent sein, aber die Variablen selbst enthalten verschiedene Dinge.a == b
und wissen können , was es tut. Ebenso kann man sehena.equals(b)
und vermuten, dass eine Überlastung vorliegtequals
. Wenna == b
Aufrufea.equals(b)
(sofern implementiert), wird der Vergleich nach Referenz oder Inhalt durchgeführt? Erinnerst du dich nicht? Sie müssen Klasse A überprüfen. Der Code ist nicht mehr so schnell lesbar, wenn Sie nicht einmal sicher sind, wie er heißt. Es ist, als ob Methoden mit derselben Signatur zulässig wären, und der aufgerufene Methodenumfang hängt vom aktuellen Gültigkeitsbereich ab. Solche Programme wären unmöglich zu lesen.Weil der letztere Ansatz verwirrend wäre. Erwägen:
Sollte dieser Code gedruckt
"ok"
werden oder sollte er einen werfenNullPointerException
?quelle
Für Java und C # liegt der Vorteil darin, dass sie objektorientiert sind.
Unter Performance-Gesichtspunkten sollte der einfacher zu schreibende Code auch schneller sein: Da OOP beabsichtigt, dass logisch unterschiedliche Elemente durch verschiedene Objekte dargestellt werden, ist die Überprüfung der Referenzgleichheit unter Berücksichtigung der Tatsache, dass Objekte sehr groß werden können, schneller.
Aus logischer Sicht - die Gleichheit eines Objekts zu einem anderen muss nicht so offensichtlich sein wie der Vergleich mit den Eigenschaften des Objekts für die Gleichheit (z. B. wie wird null == null logisch interpretiert? Dies kann von Fall zu Fall unterschiedlich sein).
Ich denke, worauf es ankommt, ist Ihre Beobachtung, dass "Sie immer logische Gleichheit über Referenzgleichheit wollen". Der Konsens unter den Sprachgestaltern war wahrscheinlich das Gegenteil. Ich persönlich finde es schwierig, dies zu bewerten, da mir das breite Spektrum an Programmiererfahrung fehlt. Grob gesagt verwende ich die Referenzgleichheit eher bei Optimierungsalgorithmen und die logische Gleichheit eher beim Umgang mit Datensätzen.
quelle
.equals()
vergleicht Variablen nach ihrem Inhalt.==
vergleicht stattdessen die Objekte nach ihrem Inhalt ...Die Verwendung von Objekten ist genauer
.equals()
quelle