Warum sollte man Objects.requireNonNull () verwenden?

213

Ich habe festgestellt, dass viele Java 8-Methoden in Oracle JDK verwenden Objects.requireNonNull(), die intern ausgelöst werden, NullPointerExceptionwenn das angegebene Objekt (Argument) istnull .

public static <T> T requireNonNull(T obj) {
    if (obj == null)
        throw new NullPointerException();
    return obj;
}

Aber NullPointerExceptionwird trotzdem , wenn ein geworfenernull Objekt dereferenziert wird. Also, warum sollte man diese zusätzliche Nullprüfung durchführen und werfen NullPointerException?

Eine offensichtliche Antwort (oder ein Vorteil) ist, dass Code dadurch besser lesbar wird, und ich stimme zu. Ich bin gespannt auf weitere Gründe für die Verwendung Objects.requireNonNull()zu Beginn der Methode.

user4686046
quelle
1
Mit diesem Ansatz zur Argumentprüfung können Sie betrügen, wenn Sie Komponententests schreiben. Spring hat auch solche Dienstprogramme (siehe docs.spring.io/spring/docs/current/javadoc-api/org/… ). Wenn Sie ein "Wenn" haben und eine hohe Einheitentestabdeckung wünschen, müssen Sie beide Zweige abdecken: Wenn die Bedingung erfüllt ist und wenn die Bedingung nicht erfüllt ist. Wenn Sie verwenden Objects.requireNonNull, hat Ihr Code keine Verzweigung, sodass Sie mit einem einzigen Durchgang eines
Komponententests eine

Antworten:

214

Weil Sie dadurch Dinge explizit machen können. Mögen:

public class Foo {
  private final Bar bar;

  public Foo(Bar bar) {
    Objects.requireNonNull(bar, "bar must not be null");
    this.bar = bar;
  }

Oder kürzer:

  this.bar = Objects.requireNonNull(bar, "bar must not be null");

Jetzt wissen Sie :

  • wenn ein Foo-Objekt erfolgreich mit erstellt wurdenew()
  • dann seine Bar ist Feld garantiert nicht-null.

Vergleichen Sie das mit: Sie erstellen heute ein Foo-Objekt und rufen morgen eine Methode auf, die dieses Feld verwendet und wirft. Höchstwahrscheinlich werden Sie morgen nicht wissen, warum diese Referenz gestern null war, als sie an den Konstruktor übergeben wurde!

Mit anderen Worten: Indem Sie diese Methode explizit verwenden, um eingehende Referenzen zu überprüfen , können Sie den Zeitpunkt steuern, zu dem die Ausnahme ausgelöst wird. Und meistens möchten Sie so schnell wie möglich scheitern !

Die Hauptvorteile sind:

  • wie gesagt, kontrolliertes Verhalten
  • Einfacheres Debuggen - weil Sie sich im Kontext der Objekterstellung übergeben. Zu einem Zeitpunkt, an dem Sie eine gewisse Chance haben, dass Ihre Protokolle / Spuren Ihnen sagen, was schief gelaufen ist!
  • und wie oben gezeigt: Die wahre Kraft dieser Idee entfaltet sich in Verbindung mit den letzten Feldern. Denn jetzt kann jeder andere Code in Ihrer Klasse davon ausgehen, dass er barnicht null ist - und Sie brauchen daher keine if (bar == null)Überprüfungen an anderen Stellen!
GhostCat
quelle
23
Sie können Ihren Code kompakter machen, indem Sie schreibenthis.bar = Objects.requireNonNull(bar, "bar must not be null");
Kirill Rakhman
3
@KirillRakhman GhostCat etwas Cooles beibringen, von dem ich nichts wusste -> ein Ticket in der GhostCat-Upvote-Lotterie zu gewinnen.
GhostCat
1
Ich denke, Kommentar Nr. 1 hier ist der eigentliche Vorteil von Objects.requireNonNull(bar, "bar must not be null");. Danke für diesen einen.
Kawu
1
Welcher war zuerst, und warum sollte „die Sprache“ Menschen folgen die Autoren einiger Bibliothek ?! Warum folgt Guave nicht dem Java-Lang-Verhalten?!
GhostCat
1
Das this.bar = Objects.requireNonNull(bar, "bar must not be null");ist in Ordnung , in dem Bau, aber potenziell gefährlich in anderen Verfahren , wenn zwei oder mehr Variablen in dem gleichen Verfahren festgelegt werden. Beispiel: talkwards.com/2018/11/03/…
Erk
99

Ausfallsicher

Der Code sollte so schnell wie möglich abstürzen. Es sollte nicht die Hälfte der Arbeit erledigen und dann die Null dereferenzieren und erst dann abstürzen, wenn die Hälfte der Arbeit erledigt bleibt und das System in einem ungültigen Zustand ist.

Dies wird allgemein als "früh scheitern" oder "schnell scheitern" bezeichnet .

luk2302
quelle
40

Die NullPointerException wird jedoch trotzdem ausgelöst, wenn ein Nullobjekt dereferenziert wird. Warum sollte man diese zusätzliche Nullprüfung durchführen und eine NullPointerException auslösen?

Dies bedeutet, dass Sie das Problem sofort und zuverlässig erkennen .

Erwägen:

  • Die Referenz kann möglicherweise erst später in der Methode verwendet werden, nachdem Ihr Code bereits einige Nebenwirkungen ausgeführt hat
  • Die Referenz darf bei dieser Methode überhaupt nicht dereferenziert werden
    • Es könnte an einen völlig anderen Code übergeben werden (dh Ursache und Fehler sind im Codebereich weit voneinander entfernt).
    • Es könnte viel später verwendet werden (dh Ursache und Fehler sind zeitlich weit voneinander entfernt)
  • Gestattet irgendwo sein , dass eine Nullreferenz ist gültig, hat aber eine unbeabsichtigte Wirkung

.NET macht dies besser, indem es trennt NullReferenceException("Sie haben einen Nullwert dereferenziert") von ArgumentNullException("Sie hätten null nicht als Argument übergeben sollen - und das war für diesen Parameter). Ich wünschte, Java hätte das Gleiche getan, aber auch mit Nur a NullPointerException, es ist immer noch viel einfacher, Code zu reparieren, wenn der Fehler an dem frühesten Punkt ausgelöst wird, an dem er erkannt werden kann.

Jon Skeet
quelle
12

Die Verwendung requireNonNull()als erste Anweisungen in einer Methode ermöglicht es, die Ursache der Ausnahme sofort / schnell zu identifizieren.
Die Stapelverfolgung zeigt deutlich an, dass die Ausnahme sofort nach Eingabe der Methode ausgelöst wurde, da der Aufrufer die Anforderungen / den Vertrag nicht eingehalten hat. Das Übergeben eines nullObjekts an eine andere Methode kann zwar jeweils eine Ausnahme hervorrufen, die Ursache des Problems kann jedoch komplizierter zu verstehen sein, da die Ausnahme in einem bestimmten Aufruf des nullObjekts ausgelöst wird , der möglicherweise viel weiter entfernt ist.


Hier ist ein konkretes und reales Beispiel, das zeigt, warum wir es vorziehen müssen, im Allgemeinen schnell zu scheitern und insbesondere Object.requireNonNull()Parameter zu verwenden oder auf irgendeine Weise eine Null-Null-Prüfung für Parameter durchzuführen, die so konzipiert sind, dass dies nicht der Fall ist null.

Angenommen , eine DictionaryKlasse , die ein komponiert LookupServiceund ein Listvon Stringdarstellt Wörter enthalten in. Diese Felder sind so konzipiert, dass nicht , nullund eine davon ist in den vergangen Dictionary Konstruktor.

Nehmen wir nun eine "schlechte" Implementierung Dictionaryohne nullÜberprüfung im Methodeneintrag an (hier ist das der Konstruktor):

public class Dictionary {

    private final List<String> words;
    private final LookupService lookupService;

    public Dictionary(List<String> words) {
        this.words = this.words;
        this.lookupService = new LookupService(words);
    }

    public boolean isFirstElement(String userData) {
        return lookupService.isFirstElement(userData);
    }        
}


public class LookupService {

    List<String> words;

    public LookupService(List<String> words) {
        this.words = words;
    }

    public boolean isFirstElement(String userData) {
        return words.get(0).contains(userData);
    }
}

Rufen wir nun den DictionaryKonstruktor mit einer nullReferenz für den wordsParameter auf:

Dictionary dictionary = new Dictionary(null); 

// exception thrown lately : only in the next statement
boolean isFirstElement = dictionary.isFirstElement("anyThing");

Die JVM wirft die NPE auf diese Aussage:

return words.get(0).contains(userData); 
Ausnahme im Thread "main" java.lang.NullPointerException
    bei LookupService.isFirstElement (LookupService.java:5)
    bei Dictionary.isFirstElement (Dictionary.java:15)
    bei Dictionary.main (Dictionary.java:22)

Die Ausnahme wird in der LookupServiceKlasse ausgelöst, während der Ursprung weit früher liegt (der DictionaryKonstruktor). Dies macht die allgemeine Problemanalyse viel weniger offensichtlich.
Ist words null? Ist words.get(0) null? Beide ? Warum sind das eine, das andere oder vielleicht beide null? Ist es ein Codierungsfehler in Dictionary(Konstruktor? Aufgerufene Methode?)? Ist es ein Codierungsfehler in LookupService? (Konstruktor? aufgerufene Methode?)?
Schließlich müssen wir mehr Code untersuchen, um den Fehlerursprung zu finden, und in einer komplexeren Klasse möglicherweise sogar einen Debugger verwenden, um leichter zu verstehen, was passiert ist.
Aber warum wird eine einfache Sache (ein Mangel an Nullprüfung) zu einem komplexen Problem?
Weil wir zugelassen haben, dass der anfängliche Fehler / Mangel bei einem bestimmten Komponentenleck bei niedrigeren Komponenten erkennbar ist.
Stell dir das vorLookupService kein lokaler Dienst, sondern ein Remote-Dienst oder eine Bibliothek eines Drittanbieters mit wenigen Debugging-Informationen, oder stellen Sie sich vor, Sie hätten nicht 2 Ebenen, sondern 4 oder 5 Ebenen von Objektaufrufen, bevor nulldiese erkannt wurden? Das Problem wäre noch komplexer zu analysieren.

Der Weg zu bevorzugen ist also:

public Dictionary(List<String> words) {
    this.words = Objects.requireNonNull(words);
    this.lookupService = new LookupService(words);
}

Auf diese Weise keine Kopfschmerzen: Wir erhalten die Ausnahme ausgelöst, sobald diese empfangen wird:

// exception thrown early : in the constructor 
Dictionary dictionary = new Dictionary(null);

// we never arrive here
boolean isFirstElement = dictionary.isFirstElement("anyThing");
Ausnahme im Thread "main" java.lang.NullPointerException
    at java.util.Objects.requireNonNull (Objects.java:203)
    at com.Dictionary. (Dictionary.java:15)
    at com.Dictionary.main (Dictionary.java:24)

Beachten Sie, dass ich hier das Problem mit einem Konstruktor veranschaulicht habe, ein Methodenaufruf jedoch dieselbe Nicht-Null-Prüfbedingung haben kann.

davidxxx
quelle
Beeindruckend! Tolle Erklärung.
Jonathas Nascimento
2

Als Randnotiz wurde dies schnell fehlgeschlagen, bevor Object#requireNotNulles vor Java-9 in einigen der JRE-Klassen selbst etwas anders implementiert wurde. Angenommen, der Fall:

 Consumer<String> consumer = System.out::println;

In Java-8 wird dies kompiliert als (nur die relevanten Teile)

getstatic Field java/lang/System.out
invokevirtual java/lang/Object.getClass

Grundsätzlich eine Operation wie: yourReference.getClass- die fehlschlagen würde, wenn Ihre Referenz ist null.

In jdk-9 haben sich die Dinge geändert, in denen derselbe Code kompiliert wird wie

getstatic Field java/lang/System.out
invokestatic java/util/Objects.requireNonNull

Oder im Grunde Objects.requireNotNull (yourReference)

Eugene
quelle
1

Die grundlegende Verwendung ist das NullPointerExceptionsofortige Überprüfen und Werfen .

Eine bessere Alternative (Verknüpfung), um dieselbe Anforderung zu erfüllen, ist die @ NoNull- Annotation von lombok.

Supun Wijerathne
quelle
1
Die Anmerkung ist kein Ersatz für die Objects-Methode, sie arbeiten zusammen. Sie können nicht sagen, @ NonNull x = könnteBeNull, Sie würden @ NonNull x = Objects.requireNonNull sagen (könnteBeNull, "unvorstellbar!");
Bill K
@ BillK Entschuldigung, ich bekomme
dich nicht
Ich habe nur gesagt, dass die Nonnull-Annotation mit requireNonNull funktioniert. Es ist keine Alternative, aber sie arbeiten recht gut zusammen.
Bill K
Um die Fail-Fast-Natur zu implementieren, ist es eine Alternative, oder? Yh Ich stimme zu, es ist keine Alternative für den Auftrag.
Supun Wijerathne
Ich würde requireNonNull () als "Konvertierung" von @ Nullable nach @ NonNull bezeichnen. Wenn Sie die Anmerkungen nicht verwenden, ist die Methode nicht wirklich sehr interessant (da sie lediglich eine NPE auslöst, genau wie der Code, den sie schützt) - obwohl sie Ihre Absicht ziemlich deutlich zeigt.
Bill K
1

Eine Nullzeigerausnahme wird ausgelöst, wenn Sie auf ein Mitglied eines Objekts zugreifen, das sich nullzu einem späteren Zeitpunkt befindet. Objects.requireNonNull()Überprüft sofort den Wert und löst sofort eine Ausnahme aus, ohne vorwärts zu gehen.

Mirwise Khan
quelle
0

Ich denke, es sollte in Kopierkonstruktoren und einigen anderen Fällen wie DI verwendet werden, deren Eingabeparameter ein Objekt ist. Sie sollten überprüfen, ob der Parameter null ist. Unter solchen Umständen können Sie diese statische Methode bequem verwenden.

Yu Chai
quelle