Abrufen eines Wertes, ohne dass Java auf null überprüft werden muss

15

Oft finde ich mich beim Abrufen eines Wertes aus einer Datenhierarchie auf Null überprüft, um NullPointerExceptions zu vermeiden, die meiner Meinung nach fehleranfällig sind und viel Boilerplate erfordern.

Ich habe eine sehr einfache Routine geschrieben, mit der ich die Nullprüfung beim Abrufen eines Objekts überspringen kann ...

public final class NoNPE {

    public static <T> T get(NoNPEInterface<T> in) {
        try {
            return in.get();
        } catch (NullPointerException e) {
            return null;
        }
    }

    public interface NoNPEInterface<T> {
        T get();
    }
}

Ich benutze es ein bisschen so ...

Room room = NoNPE.get(() -> country.getTown().getHouses().get(0).getLivingRoom());

Das obige führt dazu, dass ich ein Room-Objekt oder eine Null erhalte, ohne alle übergeordneten Ebenen überprüfen zu müssen.

Was haltet ihr von dem oben genannten? Schaffe ich ein problematisches Muster? Gibt es Ihrer Meinung nach einen besseren Weg, dies zu tun?

Eurig Jones
quelle
1
Da Sie anscheinend Java 8 verwenden, kann ich Ihnen empfehlen, Ihre Anwendung so zu gestalten, dass sie java.util.Optionalanstelle von Nullen fehlende Daten darstellt. Dies bietet praktische Hilfsprogramme sowohl für den beschriebenen Fall als auch für Fälle, in denen Sie mit Standarddaten
weitermachen möchten,
Ich denke, Sie haben im Wesentlichen die Option(oder Maybe) Monade wiederentdeckt :)
Andres F.
Möglicherweise können Sie Optional anstelle von T oder null zurückgeben. Auf diese Weise können Sie die orElse () -Methode direkt verwenden. 18 Monate später konnte aber jemand helfen.
Benj
Weitere Ansätze werden in diesem Beitrag illegalargumentexception.blogspot.com/2015/03/… erwähnt . Einer von ihnen verwendet eine Bibliothek namens kludje, die eine sehr interessante Syntax hat
Benj

Antworten:

13

Ihre Lösung ist sehr klug. Das Problem, das ich sehe, ist die Tatsache, dass Sie nicht wissen, warum Sie eine haben null? War es, weil das Haus keine Zimmer hatte? War es, weil die Stadt keine Häuser hatte? War es, weil das Land keine Städte hatte? War es, weil es nullaufgrund eines Fehlers eine 0-Position in der Sammlung gab, selbst wenn sich Häuser auf Position 1 und höher befanden?

Wenn Sie die NonPEKlasse in großem Umfang verwenden, treten schwerwiegende Fehlerbehebungsprobleme auf. Ich denke, es ist besser zu wissen, wo genau die Kette gebrochen ist, als im Stillen nulleinen Fehler zu finden, der einen tieferen Fehler verbergen könnte.

Auch verletzt dies das Gesetz von Demeter : country.getTown().getHouses().get(0).getLivingRoom(). Wenn Sie gegen einen guten Grundsatz verstoßen, müssen Sie in den meisten Fällen unorthodoxe Lösungen implementieren, um das Problem zu lösen, das durch einen Verstoß gegen diesen Grundsatz verursacht wird.

Meine Empfehlung ist, dass Sie es mit Vorsicht verwenden und versuchen, den Konstruktionsfehler zu beheben, der dazu führt, dass Sie im Zug Wrack-Antimuster haben müssen (damit Sie es nicht NonPEüberall verwenden müssen). Andernfalls können Fehler auftreten, die nur schwer zu erkennen sind.

Tulains Córdova
quelle
Gute Antwort. Ja, ich werde nicht wissen, woher ich die Null in der Kette habe. In vielen Fällen ist es mir egal, dass der Code besser lesbar und weniger anfällig für Kesselpanzerfehler ist, wenn ich keine Nullprüfung durchführen muss. Aber ja, in einigen Fällen, in denen ich eine andere logische Entscheidung treffen muss, wenn ein übergeordnetes Objekt null ist, würde dies ein Problem verursachen. Die konventionelle Methode oder die optionale Klasse könnte dort eine sicherere Lösung sein.
Eurig Jones
Wenn Sie die OptionMonade verwenden, ist es Ihnen im Allgemeinen egal, wo in der Kette der fehlende Wert ist. Wenn Sie sich dafür interessieren, würden Sie wahrscheinlich einen anderen Typ verwenden, wie z Either.
Andres F.
Der Ansatz des OP ähnelt dem von C # 6 ?.und ?[]Operatoren. Ein Beispiel dafür, wann Sie so etwas verwenden möchten, sind hierarchische serverseitige Einstellungen. var shouldDoThing = settings?.a?.b?.c ?? defaultSetting;Wen interessiert es, warum ein Teil davon null war? Möglicherweise konnten Sie die Einstellungen nicht abrufen. Vielleicht haben Sie beschlossen, einen Teil der Einstellungen zu entfernen. In jedem Fall können Sie sich nie wirklich darauf verlassen, die Servereinstellungen abzurufen. Daher ist eine Standardeinstellung in der Regel eine gute Idee, und es ist unwahrscheinlich, dass Sie die tatsächliche Einstellung nicht abrufen können, es sei denn, dies geschieht sehr häufig, wenn dies nicht der Fall ist .
Chris
Jetzt sage ich nicht, dass das besser oder schlechter ist, als die Standardeinstellungen zu lokalisieren und einfach den gewünschten Wert durch normale Zugriffe zurückzugewinnen settings.a.b.c. Andererseits ist dies ein einzelnes Beispiel.
Chris
10

Die Idee ist gut, wirklich gut. Da es die Java 8- OptionalTypen gibt, finden Sie eine ausführliche Erläuterung unter Java Optional type . Ein Beispiel für das, was Sie gepostet haben, ist

Optional.ofNullable(country)
    .map(Country::getTown)
    .map(Town::Houses);

Und weiter.

J. Pichardo
quelle
1
Ja, mir war die optionale Klasse von Java 8 und Guava bekannt, und sie ist wirklich nützlich. Sie können jedoch nicht einfach ein Objekt abrufen, da Sie den Code normalerweise ein bisschen schwerer zu lesen und ein bisschen weniger performant machen würden. Der Vorteil ist jedoch, dass die optionale Klasse viele sehr nützliche Operatoren bereitstellt.
Eurig Jones
3
@EurigJones Ich glaube nicht, dass der Code weniger performant wird. Lesbarkeit im Auge des Betrachters, aber ich würde argumentieren, Optionalist die lesbarere Lösung der beiden, wenn auch nur, weil es - anders als Ihr Vorschlag - eine sehr verbreitete Redewendung ist. Es ist noch prägnanter als deins!
Andres F.
0

Ihre Methode funktioniert gut genug für den beabsichtigten Zweck, obwohl sie nulls zurückgibt, wenn Sie ein NullPointerExceptionschlechtes Design hören.

Vermeiden Sie nulls, wenn Sie können, und geben Sie sie nur weiter, wenn sie etwas darstellen oder eine besondere Bedeutung haben, und geben Sie sie nur zurück, wenn sie etwas darstellen / bedeuten - andernfalls sollten Sie a werfen NullPointerException. Dies vermeidet Fehler und Verwirrung. Wenn ein Objectnicht sein sollte null, NullPointersollte ein geworfen werden. Wenn ein Objekt vorhanden sein kann, wird beim nullÜbergeben nichts schief gehen. Andernfalls funktioniert die obige Methode.

Luke Melaia
quelle
0

Ich kann deinen Schmerz fühlen, aber die vorgeschlagene Lösung ist eine schlechte Idee.

  • Wenn einer der Getter aus einem anderen Grund eine NPE auslöst, werden Sie diese ignorieren.
  • Es besteht die Gefahr, dass dieses innere Lambda zu schrecklichem Code wird. Wenn es zum Beispiel eine neue Anforderung gibt, eine spezielle Konstante zurückzugeben, wenn es keine Häuser in der Stadt gibt, kann ein fauler Programmierer die Lamda verlängern und alles in Hülle und Fülle lassen NoNPE.get.
  • Wie bereits erwähnt, Optional.mapist es das , wonach Sie suchen.
  • Der Aufwand für das Erstellen einer neuen Instanz von NullPointerException ist häufig erheblich. Es sind viele Mikrosekunden, besonders wenn Ihr Call-Stack immer größer wird. Es ist schwer vorherzusagen, wo Ihr Dienstprogramm letztendlich verwendet wird.

Als Randnotiz NoNPEInterfaceist ein Duplikat von java.util.function.Supplier.

In einigen Fällen können Sie die Verwendung von Ausdrucksauswertungs-Utils in Betracht ziehen, die in vielen Frameworks vorhanden sind (zum Beispiel: EL, SpEL):

evaluateProperty(country, "town.houses[0].livingRoom")
Mateusz Stefek
quelle
Für Webseitenvorlagen in Ordnung, aber im Allgemeinen langsam zu entwickeln (keine Überprüfung der Kompilierungszeit) und langsam auszuführen.
Kevin Cline