Der Vertrag in equals
Bezug auf null
lautet wie folgt:
Für jeden Nicht-Null - Referenzwert
x
,x.equals(null)
solltereturn false
.
Das ist ziemlich eigenartig, denn wenn o1 != null
und o2 == null
dann haben wir:
o1.equals(o2) // returns false
o2.equals(o1) // throws NullPointerException
Die Tatsache, dass o2.equals(o1) throws NullPointerException
das eine gute Sache ist, weil es uns vor Programmiererfehlern warnt. Und doch würde dieser Fehler nicht aufgefangen, wenn wir ihn aus verschiedenen Gründen o1.equals(o2)
einfach auf " Umschalten" umschalten würden, was stattdessen nur "stillschweigend fehlschlagen" würde.
Die Fragen sind also:
- Warum ist es eine gute Idee, die
o1.equals(o2)
solltereturn false
statt zu werfenNullPointerException
? - Wäre es eine schlechte Idee, wenn wir den Vertrag nach Möglichkeit so umschreiben, dass
anyObject.equals(null)
immerNullPointerException
stattdessen geworfen wird?
Im Vergleich zu Comparable
Im Gegensatz dazu steht im Comparable
Vertrag Folgendes:
Beachten Sie, dass dies
null
keine Instanz einer Klasse ist unde.compareTo(null)
eineNullPointerException
obwohle.equals(null)
zurückgegeben werden solltefalse
.
Wenn NullPointerException
es angemessen ist compareTo
, warum ist es nicht für equals
?
Verwandte Fragen
Ein rein semantisches Argument
Dies sind die tatsächlichen Wörter in der Object.equals(Object obj)
Dokumentation:
Gibt an, ob ein anderes Objekt diesem "gleich" ist.
Und was ist ein Objekt?
JLS 4.3.1 Objekte
Ein Objekt ist eine Klasseninstanz oder ein Array.
Die Referenzwerte (oft nur Referenzen ) sind Zeiger auf diese Objekte und eine spezielle
null
Referenz, die sich auf kein Objekt bezieht .
Mein Argument aus diesem Blickwinkel ist wirklich einfach.
equals
testet, ob ein anderes Objekt "gleich" istthis
null
Referenz gibt kein anderes Objekt für den Test- Deshalb
equals(null)
sollte werfenNullPointerException
quelle
Antworten:
Auf die Frage, ob diese Asymmetrie inkonsistent ist, denke ich nicht, und ich verweise Sie auf diesen alten Zen-Kōan:
In diesem Moment erreichte der Compiler die Erleuchtung.
quelle
Eine Ausnahme sollte wirklich eine Ausnahmesituation sein . Ein Nullzeiger ist möglicherweise kein Programmiererfehler.
Sie haben den bestehenden Vertrag zitiert. Wenn Sie sich nach all dieser Zeit gegen eine Konvention entscheiden und jeder Java-Entwickler erwartet, dass Gleiches falsch zurückgibt, werden Sie etwas Unerwartetes und Unerwünschtes tun, das Ihre Klasse zum Paria macht.
Ich könnte nicht mehr widersprechen. Ich würde nicht gleich umschreiben, um die ganze Zeit eine Ausnahme auszulösen. Ich würde jede Klasse ersetzen, die das tat, wenn ich ihr Kunde wäre.
quelle
null
Fall ist ) - aber @duffymo hat Recht und die Unterstützung für seine Antwort kommt davon, wie Java-Code selbst.equals()
für viele Klassen implementiert wird (überprüfen Sie den Quellcode). Die Gleichheit wird zuerst mit == und dann mit instanceOf geprüft. Per Definition geben diese beiden Prüfungen einen Booleschen Wert ohne zurückNullPointerException
- dh das angegebene Objekt wird in der.equals()
Methode erst verwendet , wenn nachgewiesen wurde, dass es nicht vorhanden istnull
. Wenn möglich, sollten Sie Ihre eigenen Klassen schreiben, um sich genauso zu verhalten.Überlegen Sie, wie .equals mit == und .compareTo mit den Vergleichsoperatoren>, <,> =, <= zusammenhängt.
Wenn Sie argumentieren möchten, dass die Verwendung von .equals zum Vergleichen eines Objekts mit null eine NPE auslösen sollte, müssen Sie sagen, dass dieser Code auch eine NPE auslösen sollte:
Object o1 = new Object(); Object o2 = null; boolean b = (o1 == o2); // should throw NPE here!
Der Unterschied zwischen o1.equals (o2) und o2.equals (o1) besteht darin, dass Sie im ersten Fall etwas mit null vergleichen, ähnlich wie bei o1 == o2, während im zweiten Fall die Methode equals nie tatsächlich ausgeführt wird Es findet also überhaupt kein Vergleich statt.
In Bezug auf den .compareTo-Vertrag ist der Vergleich eines Nicht-Null-Objekts mit einem Null-Objekt wie der Versuch, Folgendes zu tun:
int j = 0; if(j > null) { ... }
Offensichtlich wird dies nicht kompiliert. Sie können das automatische Entpacken verwenden, um es zu kompilieren, aber Sie erhalten beim Vergleich eine NPE, die mit dem .compareTo-Vertrag übereinstimmt:
Integer i = null; int j = 0; if(j > i) { // NPE ... }
quelle
Nicht dass dies notwendigerweise eine Antwort auf Ihre Frage ist, es ist nur ein Beispiel dafür, wann ich es nützlich finde, dass das Verhalten so ist, wie es jetzt ist.
private static final String CONSTANT_STRING = "Some value"; String text = getText(); // Whatever getText() might be, possibly returning null.
So wie es aussieht kann ich es tun.
if (CONSTANT_STRING.equals(text)) { // do something. }
Und ich habe keine Chance auf eine NullPointerException. Wenn es so geändert würde, wie Sie es vorgeschlagen haben, müsste ich wieder Folgendes tun:
if (text != null && text.equals(CONSTANT_STRING)) { // do something. }
Ist das ein guter Grund, warum das Verhalten so ist, wie es ist? Ich weiß es nicht, aber es ist eine nützliche Nebenwirkung.
quelle
Wenn Sie objektorientierte Konzepte berücksichtigen und die gesamten Sender- und Empfängerrollen berücksichtigen, würde ich sagen, dass das Verhalten praktisch ist. Sehen Sie im ersten Fall, dass Sie ein Objekt fragen, ob es niemandem gleich ist. Er sollte sagen "NEIN, ich bin nicht".
Im zweiten Fall haben Sie jedoch keinen Verweis auf jemanden. Sie fragen also niemanden wirklich. Dies sollte eine Ausnahme auslösen, der erste Fall nicht.
Ich denke, es ist nur asymmetrisch, wenn Sie die Objektorientierung vergessen und den Ausdruck als mathematische Gleichheit behandeln. In diesem Paradigma spielen jedoch beide Enden unterschiedliche Rollen, so dass zu erwarten ist, dass Ordnung wichtig ist.
Als letzter Punkt. Eine Nullzeigerausnahme sollte ausgelöst werden, wenn ein Fehler in Ihrem Code vorliegt. Ein Objekt zu fragen, ob es niemand ist, sollte jedoch nicht als Programmierfehler angesehen werden. Ich denke, es ist vollkommen in Ordnung, ein Objekt zu fragen, ob es nicht null ist. Was ist, wenn Sie die Quelle, die Sie mit dem Objekt versorgt, nicht steuern? und diese Quelle sendet Ihnen null. Würden Sie prüfen, ob das Objekt null ist und erst danach prüfen, ob es gleich ist? Wäre es nicht intuitiver, nur die beiden zu vergleichen, und was auch immer das zweite Objekt ist, der Vergleich wird ausnahmslos durchgeführt?
Um ehrlich zu sein, wäre ich sauer, wenn eine Methode equals in ihrem Körper absichtlich eine Nullzeigerausnahme zurückgibt. Equals soll für jede Art von Objekt verwendet werden, daher sollte es nicht so wählerisch sein, was es empfängt. Wenn eine gleichwertige Methode npe zurückgeben würde, wäre das Letzte, was mir in den Sinn kommt, dass sie dies absichtlich getan hat. Insbesondere wenn man bedenkt, dass es sich um eine ungeprüfte Ausnahme handelt. WENN Sie eine Npe ausgelöst haben, müsste ein Typ daran denken, immer auf Null zu prüfen, bevor er Ihre Methode aufruft, oder, noch schlimmer, den Aufruf auf Gleichheit in einem Try / Catch-Block umgeben (Gott, ich hasse Try / Catch-Blöcke). Aber na ja. ..
quelle
equals
ist ein nicht gegeben Objekt --Es ein Objekt gegeben hat Referenz . Die Frage ist wirklich "Identifiziert diese Referenz ein Objekt, das Ihnen gleich ist". Eine solche Frage kann leicht beantwortet werden: "Nein - da diese Referenz kein Objekt identifiziert, identifiziert sie sicherlich kein Objekt, das mir entspricht."Persönlich würde ich es lieber so machen, wie es ist.
Das
NullPointerException
identifiziert, dass das Problem in dem Objekt liegt, für das die Gleichheitsoperation ausgeführt wird.Wenn das
NullPointerException
wie von Ihnen vorgeschlagen verwendet wurde und Sie die (irgendwie sinnlose) Operation von ...o1.equals(o1)
Dabei ist o1 = null ... Wird dasNullPointerException
ausgelöst, weil Ihre Vergleichsfunktion verschraubt ist oder weil o1 null ist, Sie es aber nicht bemerkt haben? Ich weiß, ein extremes Beispiel, aber mit dem aktuellen Verhalten kann man leicht erkennen, wo das Problem liegt.quelle
Im ersten Fall wird
o1.equals(o2)
false zurückgegeben, dao1
ungleich ungleich isto2
, was vollkommen in Ordnung ist. Im zweiten Fall wirft esNullPointerException
weilo2
istnull
. Man kann keine Methode auf a aufrufennull
. Es mag eine Einschränkung der Programmiersprachen im Allgemeinen sein, aber wir müssen damit leben.Es ist auch keine gute Idee zu werfen, dass
NullPointerException
Sie gegen den Vertrag für dieequals
Methode verstoßen und die Dinge komplexer machen, als es sein muss.quelle
Es gibt viele häufige Situationen, in denen
null
dies in keiner Weise außergewöhnlich ist, z. B. kann es einfach den (nicht außergewöhnlichen) Fall darstellen, in dem ein Schlüssel keinen Wert hat oder auf andere Weise für „nichts“ steht. Daher ist es auch durchaus üblich,x.equals(y)
mit einem Unbekannteny
umzugehen, und immernull
zuerst nachsehen zu müssen, wäre nur vergebliche Anstrengung.Warum
null.equals(y)
ist anders? Es ist ein Programmierfehler, eine Instanzmethode für eine Nullreferenz in Java aufzurufen , und verdient daher eine Ausnahme. Die Reihenfolge vonx
undy
inx.equals(y)
sollte so gewählt werden, dassx
bekannt ist, dass dies nicht der Fall istnull
. Ich würde argumentieren, dass diese Neuordnung in fast allen Fällen auf der Grundlage dessen erfolgen kann, was zuvor über die Objekte bekannt war (z. B. von ihrem Ursprung oder durch Abgleichen gegennull
andere Methodenaufrufe).Wenn beide Objekte eine unbekannte „Nullheit“ aufweisen, muss bei anderen Codes mit ziemlicher Sicherheit mindestens eines davon überprüft werden, oder es kann nicht viel mit dem Objekt getan werden, ohne das Risiko einzugehen
NullPointerException
.Und da dies so angegeben ist, ist es ein Programmierfehler, den Vertrag zu brechen und eine Ausnahme für ein
null
Argument auszulösenequals
. Und wenn Sie die Alternative in Betracht ziehen, das Auslösen einer Ausnahme zu verlangen,equals
müsste jede Implementierung von einen Sonderfall daraus machen, und jeder Aufrufequals
eines potenziellennull
Objekts müsste vor dem Aufruf überprüft werden.Es hätte anders spezifiziert werden können (dh die Voraussetzung von
equals
würde erfordern, dass das Argument nicht istnull
), was nicht bedeutet, dass Ihre Argumentation ungültig ist, aber die aktuelle Spezifikation sorgt für eine einfachere und praktischere Programmiersprache.quelle
Beachten Sie, dass der Vertrag "für jede Nicht-Null-Referenz x" lautet. Die Implementierung sieht also folgendermaßen aus:
if (x != null) { if (x.equals(null)) { return false; } }
x
muss nichtnull
als gleich angesehen werden,null
da die folgende Definition vonequals
möglich ist:public boolean equals(Object obj) { // ... // If someMember is 0 this object is considered as equal to null. if (this.someMember == 0 and obj == null) { return true; } return false; }
quelle
Ich denke, es geht um Bequemlichkeit und vor allem um Konsistenz. Wenn Sie zulassen, dass Nullen Teil des Vergleichs sind, müssen Sie nicht
null
jedes Mal eine Überprüfung durchführen und die Semantik implementierenequals
.null
Referenzen sind in vielen Sammlungstypen legal, daher ist es sinnvoll, dass sie als rechte Seite des Vergleichs angezeigt werden.Die Verwendung von Instanzmethoden für Gleichheit, Vergleich usw. macht die Anordnung notwendigerweise asymmetrisch - ein wenig Ärger für den enormen Gewinn an Polymorphismus. Wenn ich keinen Polymorphismus benötige, erstelle ich manchmal eine symmetrische statische Methode mit zwei Argumenten
MyObject.equals(MyObjecta, MyObject b)
. Diese Methode prüft dann, ob eines oder beide Argumente Nullreferenzen sind. Wenn ich speziell Nullreferenzen ausschließen möchte, erstelle ich eine zusätzliche Methode, z. B.equalsStrict()
oder eine ähnliche, die eine Nullprüfung durchführt, bevor ich an die andere Methode delegiere.quelle
Dies ist eine schwierige Frage. Aus Gründen der Abwärtskompatibilität können Sie dies nicht tun.
Stellen Sie sich das folgende Szenario vor
void m (Object o) { if (one.equals (o)) {} else if (two.equals (o)) {} else {} }
Mit equals wird die Rückgabe der false else-Klausel ausgeführt, jedoch nicht, wenn eine Ausnahme ausgelöst wird.
Auch ist null nicht gleich "2", daher ist es absolut sinnvoll, false zurückzugeben. Dann ist es wahrscheinlich besser, auf null.equals ("b") zu bestehen, um auch false zurückzugeben :))
Diese Anforderung stellt jedoch eine seltsame und nicht symmetrische Gleichheitsbeziehung her.
quelle
Sie sollten zurückgeben,
false
wenn der Parameter istnull
.Um zu zeigen, dass dies der Standard ist, siehe '
Objects.equals(Object, Object)
fromjava.util
, der eine assymetrische Nullprüfung nur für den ersten Parameter durchführt (derequals(Object)
aufgerufen wird). Aus dem OpenJDK SE 11-Quellcode (SE 1.7 enthält genau den gleichen):public static boolean equals(Object a, Object b) { return (a == b) || (a != null && a.equals(b)); }
Dies behandelt auch zwei
null
Werte als gleich.quelle