Ist es eine schlechte Idee, wenn equals (null) stattdessen NullPointerException auslöst?

75

Der Vertrag in equalsBezug auf nulllautet wie folgt:

Für jeden Nicht-Null - Referenzwert x, x.equals(null)sollte return false.

Das ist ziemlich eigenartig, denn wenn o1 != nullund o2 == nulldann haben wir:

o1.equals(o2) // returns false
o2.equals(o1) // throws NullPointerException

Die Tatsache, dass o2.equals(o1) throws NullPointerExceptiondas 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 werfen NullPointerException?
  • Wäre es eine schlechte Idee, wenn wir den Vertrag nach Möglichkeit so umschreiben, dass anyObject.equals(null)immer NullPointerExceptionstattdessen geworfen wird?

Im Vergleich zu Comparable

Im Gegensatz dazu steht im ComparableVertrag Folgendes:

Beachten Sie, dass dies nullkeine Instanz einer Klasse ist und e.compareTo(null)eine NullPointerExceptionobwohl e.equals(null)zurückgegeben werden sollte false.

Wenn NullPointerExceptiones 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 nullReferenz, die sich auf kein Objekt bezieht .

Mein Argument aus diesem Blickwinkel ist wirklich einfach.

  • equalstestet, ob ein anderes Objekt "gleich" istthis
  • nullReferenz gibt kein anderes Objekt für den Test
  • Deshalb equals(null)sollte werfenNullPointerException
Polygenschmierstoffe
quelle
2
Hier wird kommentiert, dass es bekannt ist, dass es in Java mit equals () ganz oben in der OO-Hierarchie unmöglich ist , den equals-Vertrag für etwas anderes als den einfachsten Fall zu respektieren (dh wenn Sie dies nicht tun) OO überhaupt). Der Gedanke, dass es so etwas wie einen nicht kaputten Java equals () Vertrag gibt, ist eine Wahnvorstellung. Wir gehen viel weiter: Standardmäßig werfen equals () und hashCode () UOE. Wenn Sie diese Methoden verwenden möchten, müssen Sie dokumentieren, wie Sie mit den hier aufgeworfenen grundlegenden Problemen umgehen
SyntaxT3rr0r
1
8 Stimmen und 3 Favoriten zu meiner Frage im Zusammenhang mit der unbestreitbaren Zerbrochenheit von Gleichen hier: stackoverflow.com/questions/2205565 Die Sache ist: Die "gemeinsame Gleichheit Weisheit" funktioniert einfach nicht. Nicht nur Leute wie Joshua Bloch und Martin Odersky sagen es, sondern Sie können Logik verwenden, um diese Tatsache zu beweisen. Sie können einfach keine OOA / OOD-zu-OOP-Übersetzung durchführen und hoffen, das Java-Konzept der Gleichheit wiederzuverwenden: Für mich ist es ein grundlegender Fehler in der Sprache, die in Object gleich ist. Natürlich werden die Leute, die die Gosling-Kool-Hilfe trinken, anderer Meinung sein. Lassen Sie sie mit Bloch streiten
SyntaxT3rr0r
1
Mein letzter Punkt ist folgender: In vielen Fällen geht es nicht darum, eine NPE auszulösen oder false zurückzugeben. Es geht darum, eine große UnsupportedOperationException auszulösen, und es ist ein Java-Fehler, bei Objekten, die nicht genau das gleiche Konzept der Gleichheit haben sollten, Gleichheit aufzurufen den ersten Platz. Berühmtes letztes Wort: UnsupportedOperationException :)
SyntaxT3rr0r

Antworten:

105

Auf die Frage, ob diese Asymmetrie inkonsistent ist, denke ich nicht, und ich verweise Sie auf diesen alten Zen-Kōan:

  • Fragen Sie jeden Mann, ob er so gut ist wie der nächste Mann, und jeder wird ja sagen.
  • Fragen Sie jeden Mann, ob er so gut ist wie niemand, und jeder wird nein sagen.
  • Fragen Sie niemanden, ob es so gut ist wie irgendein Mann, und Sie werden nie eine Antwort erhalten.

In diesem Moment erreichte der Compiler die Erleuchtung.

Sean Owen
quelle
6
Ich frage mich, ob die Designer der Java-Programmiersprache wirklich daran gedacht haben oder ob sie die Asymmetrie einfach nicht bemerkt haben ... ;-)
Jesper
1
Koan wird nicht ko-an geschrieben und anscheinend befindet sich ein Makron über dem o.
Digitale Unbeständigkeit
Ha OK, ich werde es beheben, da dies komischerweise fast meine beliebteste Antwort ist. Muss es richtig machen.
Sean Owen
19

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.

Duffymo
quelle
Ich mag die Argumentation in der Frage (Sie können keine Variable verwenden, wenn dies der nullFall 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ück NullPointerException- dh das angegebene Objekt wird in der .equals()Methode erst verwendet , wenn nachgewiesen wurde, dass es nicht vorhanden ist null. Wenn möglich, sollten Sie Ihre eigenen Klassen schreiben, um sich genauso zu verhalten.
PMorganCA
8

Ü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
   ... 
}
Angus
quelle
Ich würde argumentieren, dass es einen grundlegenden Unterschied zwischen den Gleichheitsoperatoren (==) und den relationalen Operatoren (<,>, <=,> =) gibt, wenn es um Objekte geht. Die relationalen Objekte nehmen keine Objekte als Operanden (auch wenn sie vom gleichen Typ sind), daher wird stattdessen die compareTo-Methode verwendet. Andererseits ist die Methode equals kein Ersatz für den Gleichheitsoperator. Der Gleichheitsoperator kann weiterhin für Objekte verwendet werden (sofern sie vom gleichen Typ sind) und dient einem anderen Zweck als die Gleichheitsmethode.
Shazz
4

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.

DaveJohnston
quelle
4

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. ..

arg20
quelle
1
Die Frage "Bist du niemandem gleich" ist etwas zweifelhaft. Auf der anderen Seite, equalsist 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."
Supercat
Beachten Sie ferner, dass die neu formulierte Frage Gleichheitsvergleiche mit einer beliebigen Kombination von Objekttypen rechtfertigt. Jemand, der gefragt wird, ob Sie der Farbe Rot gleich sind, könnte die Frage als fehlerhaft betrachten, aber bezieht sich diese Referenz [die die Farbe Rot identifiziert] auf ein Objekt, das Ihnen entspricht? Die Referenz identifiziert etwas, das nicht der Person entspricht, daher lautet die Antwort "Nein".
Supercat
Ich verstehe den Unterschied, den Sie gemacht haben, und stimme dem zu. In der Praxis haben Sie das, was ich sage, nur mit genaueren und technischeren Begriffen gesagt. Wenn Sie aus meiner Antwort die gegenteilige Idee ziehen, habe ich mich vielleicht schlecht ausgedrückt.
arg20
Du hast dich ziemlich gut ausgedrückt; Mein Ziel war es, eine eindeutige Neuformulierung der Frage vorzuschlagen, die selbst mit einer Nullreferenz eindeutig kein Problem hätte. Es ist schade, dass es keine prägnante Notation gibt, um eine "Gleichheitsprüfung" für ein Objektpaar durchzuführen, von denen eines oder beide möglicherweise null sind.
Supercat
Eine Sache, die ich übrigens in einer modernen objektorientierten Sprache sehen möchte, wäre ein Mittel zur deklarativen oder syntaktischen Unterscheidung von Fällen, in denen eine Referenz die Identität eines Objekts kapselt , die verschiedenen Fälle, in denen sie Werte kapselt (mit der Fähigkeit zu unterscheiden verschiedene Kombinationen von geteilt gegen nicht geteilt, veränderlich gegen schreibgeschützt gegen unveränderlich und sicher zu mutieren gegen nicht stören) und der gelegentliche Fall, in dem die Referenz eine Einheit für sich sein sollte. Java ist in dieser Hinsicht sehr schwach, und leider leiht sich .NET einen Großteil seiner Schwäche aus.
Supercat
2

Persönlich würde ich es lieber so machen, wie es ist.

Das NullPointerExceptionidentifiziert, dass das Problem in dem Objekt liegt, für das die Gleichheitsoperation ausgeführt wird.

Wenn das NullPointerExceptionwie von Ihnen vorgeschlagen verwendet wurde und Sie die (irgendwie sinnlose) Operation von ...

o1.equals(o1)Dabei ist o1 = null ... Wird das NullPointerExceptionausgelö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.

Rob L.
quelle
2

Im ersten Fall wird o1.equals(o2)false zurückgegeben, da o1ungleich ungleich ist o2, was vollkommen in Ordnung ist. Im zweiten Fall wirft es NullPointerExceptionweil o2ist null. Man kann keine Methode auf a aufrufen null. 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 NullPointerExceptionSie gegen den Vertrag für die equalsMethode verstoßen und die Dinge komplexer machen, als es sein muss.

fastcodejava
quelle
2

Es gibt viele häufige Situationen, in denen nulldies 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 Unbekannten yumzugehen, und immer nullzuerst 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 von xund yin x.equals(y)sollte so gewählt werden, dass xbekannt ist, dass dies nicht der Fall ist null. 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 gegen nullandere 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 nullArgument auszulösen equals. Und wenn Sie die Alternative in Betracht ziehen, das Auslösen einer Ausnahme zu verlangen, equalsmüsste jede Implementierung von einen Sonderfall daraus machen, und jeder Aufruf equalseines potenziellen nullObjekts müsste vor dem Aufruf überprüft werden.

Es hätte anders spezifiziert werden können (dh die Voraussetzung von equalswürde erfordern, dass das Argument nicht ist null), was nicht bedeutet, dass Ihre Argumentation ungültig ist, aber die aktuelle Spezifikation sorgt für eine einfachere und praktischere Programmiersprache.

Arkku
quelle
Sie können statische Methoden für eine Nullreferenz aufrufen, sodass Ihre Antwort nicht ganz korrekt ist. Es sollte selbstverständlich sein, dass dies eine abscheulich schlechte Sache ist.
CurtainDog
1

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;
    }
}

xmuss nicht nullals gleich angesehen werden, nullda die folgende Definition von equalsmö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;
}
Vijay Mathew
quelle
1

Ich denke, es geht um Bequemlichkeit und vor allem um Konsistenz. Wenn Sie zulassen, dass Nullen Teil des Vergleichs sind, müssen Sie nicht nulljedes Mal eine Überprüfung durchführen und die Semantik implementieren equals.nullReferenzen 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.

mdma
quelle
0

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.

Anton
quelle
0

Sie sollten zurückgeben, falsewenn der Parameter ist null.

Um zu zeigen, dass dies der Standard ist, siehe ' Objects.equals(Object, Object)from java.util, der eine assymetrische Nullprüfung nur für den ersten Parameter durchführt (der equals(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 nullWerte als gleich.

Dávid Horváth
quelle