Warum verwenden C # und Java die Referenzgleichheit als Standard für '=='?

32

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?

Reißverschluss
quelle
3
Weil Variablen Referenzen sind? Da Variablen wie Zeiger wirken, ist es sinnvoll, sie so zu vergleichen
Daniel Gratzer,
C # verwendet die logische Gleichheit für Werttypen wie structs. Aber wie sollte die "logische Standardgleichheit" für zwei Objekte mit unterschiedlichen Referenztypen lauten? Oder für zwei Objekte, bei denen eines vom Typ A von B geerbt wird? Immer "falsch" wie bei Strukturen? Auch wenn Sie dasselbe Objekt zweimal referenziert haben, zuerst als A, dann als B? Macht für mich nicht viel Sinn.
Doc Brown
3
Mit anderen Worten, fragen Sie sich, warum C # Equals()das Verhalten von nicht automatisch ändert , wenn Sie es überschreiben ==?
Svick

Antworten:

29

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 == bals a.equals(b)und viel natürlicher für Programmierer mit C- oder C ++ - Erfahrung, aber es a == bist fast immer falsch. Bugs aus der Verwendung von ==wo .equalserforderlich war , hat unzählige Tausende von Programmierern Stunden verschwendet.

Kevin Cline
quelle
7
Ich denke, es gibt so viele Befürworter der Überlastung von Betreibern wie es Kritiker gibt, daher würde ich nicht sagen, dass "es eine schlechte Entscheidung war" als absolute Aussage. Beispiel: In dem C ++ - Projekt, in dem ich arbeite, haben wir die ==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. Die equals()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.
Giorgio
9
Ich würde sagen, Java hat keine benutzerdefinierte Operatorüberladung. Viele Operatoren haben in Java eine doppelte (überladene) Bedeutung. Schauen Sie sich +zum Beispiel, die zusätzlich tut (numerischer Werte) und Verkettung von Strings zur gleichen Zeit.
Joachim Sauer
14
Wie kann a == bes 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 Zeichenfolgen strcmp(a, b) == 0nicht a == b.)
Svick
Dies ist im Grunde das, was ich dachte, aber ich dachte, ich würde diejenigen mit mehr Erfahrung fragen, um sicherzustellen, dass mir nichts Offensichtliches entgeht.
Reißverschluss
4
@svick: In C gibt es weder einen Zeichenfolgentyp noch Referenztypen. Zeichenkettenoperationen werden über durchgeführt char *. Mir scheint klar, dass der Vergleich von zwei Zeigern auf Gleichheit nicht mit einem Zeichenfolgenvergleich identisch ist.
Kevin Cline
15

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:

  • Referenzgleichheit : bedeutet, dass a = b wahr ist, wenn sich a und b auf dasselbe Objekt beziehen. Es wäre nicht wahr, wenn sich a und b auf verschiedene Objekte beziehen würden, selbst wenn alle Attribute von a und b gleich wären.
  • Flache Gleichheit : bedeutet, dass a = b wahr ist, wenn alle Attribute der Objekte, auf die sich a und b beziehen, identisch sind. Flache Gleichheit kann leicht durch einen bitweisen Vergleich des Speicherbereichs implementiert werden, der die beiden Objekte darstellt. Bitte beachten Sie, dass Referenzgleichheit eine geringe Gleichheit impliziert
  • Tiefe Gleichheit : bedeutet, dass a = b wahr ist, wenn jedes Attribut in a und b entweder identisch oder tief gleich ist. Bitte beachten Sie, dass eine tiefe Gleichheit sowohl von Referenzgleichheit als auch von flacher Gleichheit impliziert wird. In diesem Sinne ist tiefe Gleichheit die schwächste Form der Gleichheit und Referenzgleichheit die stärkste.

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:

Gibt es einen großen Vorteil des Standardwerts, den ich einfach vermisse, oder erscheint es vernünftig, dass das Standardverhalten logische Gleichheit ist und standardmäßig auf Referenzgleichheit zurückgegriffen wird, wenn für die Klasse keine logische Gleichheit vorhanden ist?

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:

var a = 1;
var b = a;
if (a == b){
    ...
}
a = 3;
b = 3;
if (a == b) {
    ...
}

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:

var a = new Something(1);
var b = a;
if (a == b){
    ...
}
b = new Something(1);
a.DoSomething();
b.DoSomething();
if (a == b) {
    ...
}

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.

Hbas
quelle
2
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.
Charles Salvia
Sie verwendeten "das Äquivalent der Bezugsgleichheit" auch für Primitive. Wenn Sie "int i = 3" verwenden, gibt es keine Zeiger für die Zahl, daher können Sie keine Referenzen verwenden. Bei Strings, einer Art primitivem Typ, ist es offensichtlicher: Sie müssen ".intern ()" oder eine direkte Zuweisung (String s = "abc") verwenden, um == (Referenzgleichheit) zu verwenden.
Hbas
1
PS: C # hingegen stimmte nicht mit den Saiten überein. Und IMHO, in diesem Fall ist das viel besser.
Hbas
@CharlesSalvia: In Java, wenn aund bvom gleichen Typ ist , der Ausdruck a==büberprüft , ob aund bhalten 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.
Superkatze
2
@CharlesSalvia Es ist so konzipiert, dass Sie sehen a == bund wissen können , was es tut. Ebenso kann man sehen a.equals(b)und vermuten, dass eine Überlastung vorliegt equals. Wenn a == bAufrufe a.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.
Neil
0

Ich habe versucht zu überlegen, warum diese beiden Sprachen diesen Weg gegangen sind, anstatt ihn zu invertieren und == logische Gleichheit zu haben und .ReferenceEquals () als Referenzgleichheit zu verwenden.

Weil der letztere Ansatz verwirrend wäre. Erwägen:

if (null.ReferenceEquals(null)) System.out.println("ok");

Sollte dieser Code gedruckt "ok"werden oder sollte er einen werfen NullPointerException?

Atsby
quelle
-2

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.

Rafael Emshoff
quelle
7
Bezugsgleichheit hat nichts mit Objektorientierung zu tun. Im Gegenteil: Eine der grundlegenden Eigenschaften der Objektorientierung ist, dass Objekte, die dasselbe Verhalten haben, nicht zu unterscheiden sind. Ein Objekt muss ein anderes Objekt simulieren können. (Immerhin wurde OO für die Simulation erfunden!) Mit der Referenzgleichheit können Sie zwischen zwei verschiedenen Objekten mit demselben Verhalten unterscheiden, und Sie können zwischen einem simulierten und einem realen Objekt unterscheiden. Daher unterbricht Referenzgleichung die Objektorientierung. Ein OO-Programm darf nicht Reference Equality verwenden.
Jörg W Mittag
@ JörgWMittag: Um ein objektorientiertes Programm korrekt ausführen zu können, muss es ein Mittel geben, um das Objekt X zu fragen, ob sein Zustand dem von Y entspricht [ein potenziell vorübergehender Zustand], und ein Mittel, um das Objekt X zu fragen, ob es Y entspricht [X ist nur dann gleich Y, wenn sein Zustand garantiert ewig gleich Y ist]. Es wäre gut, getrennte virtuelle Methoden für Äquivalenz und Zustandsgleichheit zu haben, aber für viele Typen impliziert die Referenzungleichheit eine Nichtäquivalenz, und es gibt keinen Grund, Zeit für den Versand virtueller Methoden zu investieren, um dies zu beweisen.
Superkatze
-3

.equals()vergleicht Variablen nach ihrem Inhalt. ==vergleicht stattdessen die Objekte nach ihrem Inhalt ...

Die Verwendung von Objekten ist genauer .equals()

Nuno Dias
quelle
3
Ihre Annahme ist falsch. .equals () macht, wozu .equals () codiert wurde. Es ist normalerweise inhaltlich, muss es aber nicht sein. Es ist auch nicht genauer .equals () zu verwenden. Es kommt nur darauf an, was Sie erreichen wollen.
Reißverschluss