Java "?" Operator zur Überprüfung von null - Was ist das? (Nicht ternär!)

85

Ich habe einen Artikel gelesen, der aus einer Slashdot-Geschichte stammt, und bin auf diesen kleinen Leckerbissen gestoßen:

Nehmen Sie die neueste Version von Java, die versucht, die Überprüfung von Nullzeigern zu vereinfachen, indem sie eine Kurzsyntax für das Testen endloser Zeiger bietet. Das Hinzufügen eines Fragezeichens zu jedem Methodenaufruf enthält automatisch einen Test für Nullzeiger, der das Nest einer Wenn-Dann-Anweisung einer Ratte ersetzt, wie z.

    public String getPostcode(Person person) {
      String ans= null;
      if (person != null) {
        Name nm= person.getName();
        if (nm!= null) {
          ans= nm.getPostcode();
        }
      }
      return ans
    } 

Mit diesem:

public String getFirstName(Person person) {
      return person?.getName()?.getGivenName();
    } 

Ich habe das Internet durchsucht (okay, ich habe mindestens 15 Minuten damit verbracht, Variationen von "Java-Fragezeichen" zu googeln) und nichts bekommen. Meine Frage: Gibt es dazu eine offizielle Dokumentation? Ich habe festgestellt, dass C # einen ähnlichen Operator hat (den Operator "??"), aber ich möchte die Dokumentation für die Sprache erhalten, in der ich arbeite. Oder ist dies nur eine Verwendung des ternären Operators, den ich habe noch nie vorher gesehen.

Vielen Dank!

BEARBEITEN: Link zum Artikel: http://infoworld.com/d/developer-world/12-programming-mistakes-avoid-292

Erty Seidohl
quelle
3
Könnten wir wenigstens einen Link zum Artikel haben?
Karl Knechtel
Und die Quelle des Schnipsels?
Khachik
5
Der Artikel ist falsch. infoworld.com/print/145292 Ich glaube, es wurde eine Projektmünze eingereicht. Aber es wurde nicht ausgewählt (aus den im Artikel genannten Gründen - wenn Sie so etwas tun möchten, verwenden Sie C # oder so) und ist sicherlich nicht in der aktuellen Version der Java-Sprache.
Tom Hawtin - Tackline
4
Das ist nicht dasselbe wie das C # ?? Operator: ?? verschmilzt Nullen, dh A ?? B == (A != null) ? A : B. Dies scheint eine Eigenschaft für ein Objekt auszuwerten, wenn die Objektreferenz nicht null ist, d A?.B == (A != null) ? A.B : null. H.
Rup
1
@Erty: Als großer Benutzer der @ NotNull-Annotation, die im Grunde überall in dem von mir geschriebenen Code vorkommt, weiß ich nicht mehr genau, was NPEs sind (außer wenn schlecht desingte APIs verwendet werden). Dennoch finde ich diese "Abkürzungsnotation" süß und interessant. Natürlich ist der Artikel richtig, wenn er besagt: Schließlich beseitigt er nicht die Wurzel des Problems: die Verbreitung von Nullwerten aufgrund schneller und loser Programmierung. 'null' existiert nicht auf OOA / OOD-Ebene. Es ist ein weiterer Java-idiosynchratischer Unsinn, der meistens umgangen werden kann. Für mich ist es überall @NotNull .
SyntaxT3rr0r

Antworten:

78

Die ursprüngliche Idee kommt von groovy. Es wurde für Java 7 als Teil von Project Coin vorgeschlagen: https://wiki.openjdk.java.net/display/Coin/2009+Proposals+TOC (Elvis und andere null-sichere Operatoren), wurde jedoch noch nicht akzeptiert .

Der verwandte Elvis-Operator ?: Wurde vorgeschlagen, um eine x ?: yAbkürzung für zu verwenden x != null ? x : y, die besonders nützlich ist, wenn x ein komplexer Ausdruck ist.

ataylor
quelle
3
In Java (wo es keinen automatischen Zwang gibt, null zu machen) eine Abkürzung fürx!=null ? x : y
Michael Borgwardt
@ Michael Borgwardt: Guter Punkt, ich habe an die groovige Semantik gedacht.
Ataylor
50
?ist Elvis Presleys Unterschriftenquiff; Das :Just repräsentiert wie gewohnt ein Paar Augen. Vielleicht ?:-oist es eindrucksvoller ...
Andrzej Doyle
4
?:0Sollte ein Operator "Nicht null oder 0" sein. Mach es so.
Azz
3
Es war tatsächlich ein Fehler, den Vorschlag als Elvis-Betreiber zu bezeichnen. Erläuterung unter mail.openjdk.java.net/pipermail/coin-dev/2009-July/002089.html "Null-sichere Dereferenzierung" ist ein besserer Begriff.
JustinKSU
62

Diese Syntax existiert weder in Java noch soll sie in einer der mir bekannten kommenden Versionen enthalten sein.

ColinD
quelle
9
Warum das Downvote? Diese Antwort ist meines Wissens zu 100% richtig. Wenn Sie etwas anderes wissen, sagen Sie es bitte.
ColinD
6
@Webinator: Es wird nicht in Java 7 oder 8 sein, und es gibt derzeit keine anderen "kommenden Versionen". Ich finde es auch irgendwie unwahrscheinlich, dass es gelingt, da es eher schlechte Praktiken fördert. Ich denke auch nicht, dass "noch" notwendig ist, da "existiert nicht in Java" nicht dasselbe ist wie "wird niemals in Java existieren".
ColinD
9
@Webinator: Mehrere Poster haben kommentiert, dass ein Vorschlag eingereicht, aber abgelehnt wurde. Somit ist die Antwort 100% genau. Upvoting, um der Abwertung entgegenzuwirken.
JeremyP
2
@ColinD schlechte Praxis ist, wenn Sie diesen hässlichen Code aufgeben und sich entscheiden, Optionalund mapSachen zu verwenden. Wir verbergen das Problem nicht, wenn der Wert nullbar ist, was bedeutet, dass manchmal erwartet wird, dass er null ist, und Sie müssen damit umgehen. Manchmal sind Standardwerte durchaus vernünftig und keine schlechte Praxis.
M. Kazem Akhgary 14.
2
Java weigert sich einfach, etwas modern zu sein. Beenden Sie diese Sprache einfach schon.
Plagon
19

Ein Weg, um das Fehlen von "?" Der Operator, der Java 8 ohne den Aufwand von try-catch verwendet (der NullPointerException, wie erwähnt, auch einen Ursprung an anderer Stelle verbergen könnte ), erstellt eine Klasse für "Pipe" -Methoden in einem Java-8-Stream-Stil.

public class Pipe<T> {
    private T object;

    private Pipe(T t) {
        object = t;
    }

    public static<T> Pipe<T> of(T t) {
        return new Pipe<>(t);
    }

    public <S> Pipe<S> after(Function<? super T, ? extends S> plumber) {
        return new Pipe<>(object == null ? null : plumber.apply(object));
    }

    public T get() {
        return object;
    }

    public T orElse(T other) {
        return object == null ? other : object;
    }
}

Dann würde das gegebene Beispiel werden:

public String getFirstName(Person person) {
    return Pipe.of(person).after(Person::getName).after(Name::getGivenName).get();
}

[BEARBEITEN]

Bei weiteren Überlegungen stellte ich fest, dass es tatsächlich möglich ist, dasselbe nur mit Standard-Java 8-Klassen zu erreichen:

public String getFirstName(Person person) {
    return Optional.ofNullable(person).map(Person::getName).map(Name::getGivenName).orElse(null);
}

In diesem Fall ist es sogar möglich, einen Standardwert (wie "<no first name>") auszuwählen, anstatt nullihn als Parameter von zu übergeben orElse.

Helder Pereira
quelle
Ihre erste Lösung gefällt mir besser. Wie würden Sie Ihre PipeKlasse verbessern , um eine orElseFunktionalität so anzupassen , dass ich ein beliebiges Nicht-Null-Objekt innerhalb der orElseMethode übergeben kann?
ThanosFisherman
@ThanosFisherman Ich habe die orElseMethode zur PipeKlasse hinzugefügt .
Helder Pereira
7

Das ist eigentlich Groovys Safe-Dereferenzierungs-Operator . Sie können es nicht in reinem Java verwenden (leider), so dass dieser Beitrag einfach falsch ist (oder eher leicht irreführend, wenn behauptet wird, Groovy sei die "neueste Version von Java").

Andrzej Doyle
quelle
2
Der Artikel war also falsch - die Syntax existiert in nativem Java nicht. Hmm.
Erty Seidohl
aber die Verbindung ist unterbrochen
bvdb
5

Java hat nicht die genaue Syntax, aber ab JDK-8 verfügen wir über die optionale API mit verschiedenen Methoden. Also die C # -Version unter Verwendung des bedingten Nulloperators :

return person?.getName()?.getGivenName(); 

kann in Java mit der optionalen API wie folgt geschrieben werden :

 return Optional.ofNullable(person)
                .map(e -> e.getName())
                .map(e -> e.getGivenName())
                .orElse(null);

Wenn einer von oder null ist person, wird null zurückgegeben.getNamegetGivenName

Ousmane D.
quelle
2

Es ist möglich, util-Methoden zu definieren, die dies mit Java 8 Lambda auf fast hübsche Weise lösen.

Dies ist eine Variante der H-MAN-Lösung , verwendet jedoch überladene Methoden mit mehreren Argumenten, um mehrere Schritte zu verarbeiten, anstatt sie abzufangen NullPointerException.

Selbst wenn ich denke, dass diese Lösung irgendwie cool ist, bevorzuge ich Helder Pereiras zweite, da dies keine nützlichen Methoden erfordert.

void example() {
    Entry entry = new Entry();
    // This is the same as H-MANs solution 
    Person person = getNullsafe(entry, e -> e.getPerson());    
    // Get object in several steps
    String givenName = getNullsafe(entry, e -> e.getPerson(), p -> p.getName(), n -> n.getGivenName());
    // Call void methods
    doNullsafe(entry, e -> e.getPerson(), p -> p.getName(), n -> n.nameIt());        
}

/** Return result of call to f1 with o1 if it is non-null, otherwise return null. */
public static <R, T1> R getNullsafe(T1 o1, Function<T1, R> f1) {
    if (o1 != null) return f1.apply(o1);
    return null; 
}

public static <R, T0, T1> R getNullsafe(T0 o0, Function<T0, T1> f1, Function<T1, R> f2) {
    return getNullsafe(getNullsafe(o0, f1), f2);
}

public static <R, T0, T1, T2> R getNullsafe(T0 o0, Function<T0, T1> f1, Function<T1, T2> f2, Function<T2, R> f3) {
    return getNullsafe(getNullsafe(o0, f1, f2), f3);
}


/** Call consumer f1 with o1 if it is non-null, otherwise do nothing. */
public static <T1> void doNullsafe(T1 o1, Consumer<T1> f1) {
    if (o1 != null) f1.accept(o1);
}

public static <T0, T1> void doNullsafe(T0 o0, Function<T0, T1> f1, Consumer<T1> f2) {
    doNullsafe(getNullsafe(o0, f1), f2);
}

public static <T0, T1, T2> void doNullsafe(T0 o0, Function<T0, T1> f1, Function<T1, T2> f2, Consumer<T2> f3) {
    doNullsafe(getNullsafe(o0, f1, f2), f3);
}


class Entry {
    Person getPerson() { return null; }
}

class Person {
    Name getName() { return null; }
}

class Name {
    void nameIt() {}
    String getGivenName() { return null; }
}
Lii
quelle
1

Ich bin mir nicht sicher, ob das überhaupt funktionieren würde. Wenn beispielsweise die Personenreferenz null wäre, durch was würde die Laufzeit sie ersetzen? Eine neue Person? Dies würde erfordern, dass die Person über eine Standardinitialisierung verfügt, die Sie in diesem Fall erwarten würden. Sie können Nullreferenzausnahmen vermeiden, aber Sie würden immer noch ein unvorhersehbares Verhalten erhalten, wenn Sie diese Art von Setups nicht geplant hätten.

Das ?? Der Operator in C # kann am besten als "Koaleszenz" -Operator bezeichnet werden. Sie können mehrere Ausdrücke verketten und es wird der erste zurückgegeben, der nicht null ist. Java hat es leider nicht. Ich denke, das Beste, was Sie tun können, ist, den ternären Operator zu verwenden, um Nullprüfungen durchzuführen und eine Alternative zum gesamten Ausdruck zu bewerten, wenn ein Mitglied in der Kette null ist:

return person == null ? "" 
    : person.getName() == null ? "" 
        : person.getName().getGivenName();

Sie können auch try-catch verwenden:

try
{
   return person.getName().getGivenName();
}
catch(NullReferenceException)
{
   return "";
}
KeithS
quelle
1
"Womit würde die Laufzeit es ersetzen?" ... das Lesen der Frage könnte helfen :-P Es würde sie durch null ersetzen. Im Allgemeinen scheint die Idee zu sein, dass diese Person? .GetName null ergibt, wenn person null ist, oder person.getName, wenn nicht. Es ist also so ziemlich so, als würde man in all Ihren Beispielen "" durch null ersetzen.
Subsub
1
Eine Nullreferenceexception könnte auch in geworfen getName()oder getGivenName()die Sie nicht wissen , ob Sie einfach eine leere Zeichenfolge für alle Vorkommen zurück.
Jimmy T.
1
In Java ist es NullPointerException.
Tuupertunut
in C # person?.getName()?.getGivenName() ?? ""ist das Äquivalent Ihres ersten Beispiels, mit der Ausnahme, dass wenn getGivenName()null zurückgegeben wird, es immer noch gibt""
Austin_Anderson
0

Da haben Sie es, null-sicherer Aufruf in Java 8:

public void someMethod() {
    String userName = nullIfAbsent(new Order(), t -> t.getAccount().getUser()
        .getName());
}

static <T, R> R nullIfAbsent(T t, Function<T, R> funct) {
    try {
        return funct.apply(t);
    } catch (NullPointerException e) {
        return null;
    }
}
H-MAN
quelle
Ich werde das versuchen müssen. SI hat ernsthafte Zweifel an diesem ganzen "optionalen" Geschäft. Scheint wie ein böser Hack.
ggb667
3
Der gesamte Zweck des Vorschlags des Elvis-Betreibers bestand darin, dies in einer Zeile zu tun. Dieser Ansatz ist nicht besser als der oben beschriebene "if (! = Null) -Ansatz. Tatsächlich würde ich behaupten, dass er schlechter ist, da er nicht einfach ist. Außerdem sollten Sie vermeiden, Fehler aufgrund des Overheads zu werfen und zu fangen.
JustinKSU
Wie @Darron in einer anderen Antwort sagte, gilt das Gleiche hier: "Das Problem bei diesem Stil ist, dass die NullPointerException möglicherweise nicht von der Stelle stammt, an der Sie sie erwartet haben. Und somit kann sie einen echten Fehler verbergen."
Helder Pereira
0

Wenn jemand nach einer Alternative für alte Java-Versionen sucht, können Sie diese ausprobieren, die ich geschrieben habe:

/**
 * Strong typed Lambda to return NULL or DEFAULT VALUES instead of runtime errors. 
 * if you override the defaultValue method, if the execution result was null it will be used in place
 * 
 * 
 * Sample:
 * 
 * It won't throw a NullPointerException but null.
 * <pre>
 * {@code
 *  new RuntimeExceptionHandlerLambda<String> () {
 *      @Override
 *      public String evaluate() {
 *          String x = null;
 *          return x.trim();
 *      }  
 *  }.get();
 * }
 * <pre>
 * 
 * 
 * @author Robson_Farias
 *
 */

public abstract class RuntimeExceptionHandlerLambda<T> {

    private T result;

    private RuntimeException exception;

    public abstract T evaluate();

    public RuntimeException getException() {
        return exception;
    }

    public boolean hasException() {
        return exception != null;
    }

    public T defaultValue() {
        return result;
    }

    public T get() {
        try {
            result = evaluate();
        } catch (RuntimeException runtimeException) {
            exception = runtimeException;
        }
        return result == null ? defaultValue() : result;
    }

}
irobson
quelle
0

Sie können den von Ihnen bereitgestellten Code testen und es wird ein Syntaxfehler ausgegeben. Daher wird er in Java nicht unterstützt. Groovy unterstützt es und es wurde für Java 7 vorgeschlagen (wurde aber nie aufgenommen).

Sie können jedoch die in Java 8 bereitgestellte Option verwenden. Dies kann Ihnen dabei helfen, etwas in einer ähnlichen Zeile zu erreichen. https://docs.oracle.com/javase/8/docs/api/java/util/Optional.html http://www.oracle.com/technetwork/articles/java/java8-optional-2175753.html

Beispielcode für Optional

SK -
quelle
0

Da Android Lambda-Funktionen nur unterstützt, wenn Ihr installiertes Betriebssystem> = 24 ist, müssen wir Reflection verwenden.

// Example using doIt function with sample classes
public void Test() {
    testEntry(new Entry(null));
    testEntry(new Entry(new Person(new Name("Bob"))));
}

static void testEntry(Entry entry) {
    doIt(doIt(doIt(entry,  "getPerson"), "getName"), "getName");
}

// Helper to safely execute function 
public static <T,R> R doIt(T obj, String methodName) {
    try {
       if (obj != null) 
           return (R)obj.getClass().getDeclaredMethod(methodName).invoke(obj);
    } catch (Exception ignore) {
    }
    return null;
}
// Sample test classes
    static class Entry {
        Person person;
        Entry(Person person) { this.person = person; }
        Person getPerson() { return person; }
    }

    static class Person {
        Name name;
        Person(Name name) { this.name = name; }
        Name getName() { return name; }
    }

    static class Name {
        String name;
        Name(String name) { this.name = name; }
        String getName() {
            System.out.print(" Name:" + name + " ");
            return name;
        }
    }
}
LanDenLabs
quelle
-4

Wenn dies für Sie kein Leistungsproblem ist, können Sie schreiben

public String getFirstName(Person person) {
  try {
     return person.getName().getGivenName();
  } catch (NullPointerException ignored) {
     return null;
  }
} 
Peter Lawrey
quelle
8
Das Problem bei diesem Stil ist, dass die NullPointerException möglicherweise nicht von der Stelle stammt, an der Sie sie erwartet haben. Und so kann es einen echten Fehler verbergen.
Darron
2
@Darron, kannst du ein Beispiel für einen Getter geben, den du geschrieben hast, der eine NPE werfen könnte und wie du anders damit umgehen möchtest?
Peter Lawrey
2
Sie führen es in eine separate Variable aus und testen es wie gewohnt mit einem if. Die Idee hinter diesem Operator ist es, diese Hässlichkeit zu beseitigen. Darron hat recht, Ihre Lösung könnte Ausnahmen, die Sie werfen möchten, verbergen und wegwerfen. getName()Zum Beispiel, wenn intern eine Ausnahme ausgelöst wurde, die Sie nicht wegwerfen möchten.
Mike Miller
2
Keine Ausnahme, es müsste eine NullPointerException sein. Sie versuchen, sich vor einer Situation zu schützen, in der Sie noch nicht erklärt haben, wie dies in einer realen Anwendung auftreten kann.
Peter Lawrey
1
In einer realen Anwendung Personkönnte ein Proxy auf eine Datenbank oder einen Nicht-Heap-Speicher zugreifen, und irgendwo könnte ein Fehler auftreten ... Nicht sehr realistisch, aber Peter, ich wette, Sie haben noch nie einen Code wie oben geschrieben .
Maaartinus