Java "?" Operatore per il controllo di null - Che cos'è? (Non ternario!)


88

Stavo leggendo un articolo collegato a una storia di slashdot e mi sono imbattuto in questo piccolo bocconcino:

Prendi l'ultima versione di Java, che cerca di rendere più facile il controllo del puntatore nullo offrendo una sintassi abbreviata per il test infinito del puntatore. La semplice aggiunta di un punto interrogativo a ciascuna invocazione di metodo include automaticamente un test per i puntatori nulli, sostituendo un ratto di istruzioni if-then, come:

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

Con questo:

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

Ho setacciato Internet (okay, ho passato almeno 15 minuti a cercare variazioni su Google su "punto interrogativo java") e non ho ottenuto nulla. Quindi, la mia domanda: c'è qualche documentazione ufficiale su questo? Ho scoperto che C # ha un operatore simile (l'operatore "??"), ma mi piacerebbe ottenere la documentazione per il linguaggio in cui sto lavorando. Oppure questo è solo un uso dell'operatore ternario che ho mai visto prima.

Grazie!

EDIT: Link all'articolo: http://infoworld.com/d/developer-world/12-programming-mistakes-avoid-292


3
Potremmo avere almeno un link all'articolo?
Karl Knechtel

1
E la fonte dello snippet?
khachik

5
L'articolo è sbagliato. infoworld.com/print/145292 Credo che sia stata inviata una presentazione di Project Coin per questo. Ma non è stato selezionato (per i motivi menzionati nell'articolo - se vuoi fare quel genere di cose, usa C # o qualcosa del genere) e certamente non è nella versione corrente del linguaggio Java.
Tom Hawtin - tackline

4
Non è lo stesso di C # ?? operatore: ?? unisce i valori nulli, ovvero A ?? B == (A != null) ? A : B. Questo sembra valutare una proprietà su un oggetto se il riferimento all'oggetto non è nullo, ad es A?.B == (A != null) ? A.B : null.
Rup

1
@Erty: in quanto utente enorme dell'annotazione @NotNull, che è praticamente ovunque nel codice che scrivo, non so più molto bene cosa siano gli NPE (tranne quando si usano API mal progettate). Eppure trovo questa "notazione scorciatoia" carina e interessante. Ovviamente l'articolo ha ragione quando afferma: Dopotutto, non elimina la radice del problema: la proliferazione di valori nulli a causa della programmazione veloce e libera. "null" non esiste a livello OOA / OOD. È un'altra sciocchezza idiosincratica di Java che può essere per lo più aggirata. Per me è @NotNull ovunque.
SyntaxT3rr0r

Risposte:


80

L'idea originale viene da groovy. È stato proposto per Java 7 come parte di Project Coin: https://wiki.openjdk.java.net/display/Coin/2009+Proposals+TOC (Elvis and Other Null-Safe Operators), ma non è stato ancora accettato .

L'operatore di Elvis correlato?: È stato proposto per x ?: yabbreviare x != null ? x : y, particolarmente utile quando x è un'espressione complessa.


3
In java (dove non esiste l'auto-coercizione per annullare) una scorciatoia perx!=null ? x : y
Michael Borgwardt

@Michael Borgwardt: buon punto, stavo pensando alla semantica groovy.
ataylor

50
?è il quiff caratteristico di Elvis Presley; il :solo rappresenta un paio di occhi come al solito. Forse ?:-oè più evocativo ...
Andrzej Doyle

4
?:0Dovrebbe essere un operatore "Non nullo, né 0". Fallo così.
azz

3
In realtà è stato un errore fare riferimento alla proposta come un operatore Elvis. Spiegazione su mail.openjdk.java.net/pipermail/coin-dev/2009-July/002089.html "Null-safe dereferencing" è un termine migliore da usare.
JustinKSU

63

Questa sintassi non esiste in Java, né è prevista per essere inclusa in nessuna delle prossime versioni di cui sono a conoscenza.


9
Perché il voto negativo? Questa risposta è corretta al 100% per quanto ne so ... se conosci qualcosa di diverso, per favore dillo.
ColinD

6
@Webinator: Non sarà in Java 7 o 8 e non ci sono altre "versioni imminenti" in questo momento. Trovo anche un po 'improbabile che ce la farà, poiché incoraggia pratiche piuttosto cattive. Inoltre non penso che "ancora" sia necessario, poiché "non esiste in Java" non è la stessa cosa di "non esisterà mai in Java".
ColinD

9
@Webinator: diversi poster hanno commentato che una proposta è stata presentata ma respinta. Quindi la risposta è accurata al 100%. Upvoting per contrastare il voto negativo.
JeremyP

2
@ColinD è una cattiva pratica quando si rinuncia a questo codice brutto e si decide di usare Optionale mapcose del genere. non stiamo nascondendo il problema se il valore è nullable, il che significa che a volte ci si aspetta che sia nullo e che devi gestirlo. a volte i valori predefiniti sono perfettamente ragionevoli e non è una cattiva pratica.
M.kazem Akhgary

2
Java si rifiuta semplicemente di essere un po 'moderno. Basta terminare già questa lingua.
Plagon


19

Un modo per aggirare la mancanza di "?" L'operatore che utilizza Java 8 senza l'overhead di try-catch (che potrebbe anche nascondere un file NullPointerExceptionoriginato altrove, come detto) consiste nel creare una classe per "pipe" metodi in stile Java-8-Stream.

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

Quindi, l'esempio fornito diventerebbe:

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

[MODIFICARE]

Dopo ulteriori riflessioni, ho capito che in realtà è possibile ottenere lo stesso risultato solo utilizzando classi Java 8 standard:

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

In questo caso, è anche possibile scegliere un valore predefinito (like "<no first name>") invece di nullpassarlo come parametro di orElse.


Mi piace di più la tua prima soluzione. Come miglioreresti la tua Pipeclasse per adattare una orElsefunzionalità in modo da poter passare un oggetto arbitrario non nullo all'interno del orElsemetodo?
ThanosFisherman

@ThanosFisherman ho aggiunto il orElsemetodo alla Pipeclasse.
Helder Pereira,



6

Java non ha la sintassi esatta ma a partire da JDK-8, abbiamo l' API opzionale con vari metodi a nostra disposizione. Quindi, la versione C # con l'uso dell'operatore condizionale null :

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

può essere scritto come segue in Java con l' API opzionale :

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

se uno di person, getNameo getGivenNameè nullo, viene restituito nullo.


2

È possibile definire metodi util che risolvano questo problema in un modo quasi carino con Java 8 lambda.

Questa è una variazione della soluzione di H-MAN ma utilizza metodi sovraccaricati con più argomenti per gestire più passaggi invece di catturare NullPointerException.

Anche se penso che questa soluzione sia piuttosto interessante, penso di preferire quella dei secondi di Helder Pereira poiché non richiede alcun metodo di utilizzo.

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

1

Non sono sicuro che questo potrebbe funzionare; se, ad esempio, il riferimento alla persona fosse nullo, con cosa lo sostituirebbe il runtime? Una nuova persona? Ciò richiederebbe alla persona di avere un'inizializzazione predefinita che ti aspetteresti in questo caso. È possibile evitare eccezioni di riferimento nullo, ma si otterrebbe comunque un comportamento imprevedibile se non si pianificasse questo tipo di configurazione.

Il ?? l'operatore in C # potrebbe essere meglio definito l'operatore "coalesce"; puoi concatenare diverse espressioni e restituirà la prima che non è nulla. Sfortunatamente, Java non ce l'ha. Penso che il meglio che potresti fare è usare l'operatore ternario per eseguire controlli nulli e valutare un'alternativa all'intera espressione se qualsiasi membro della catena è nullo:

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

Puoi anche usare try-catch:

try
{
   return person.getName().getGivenName();
}
catch(NullReferenceException)
{
   return "";
}

1
"con cosa lo sostituirà il runtime?" ... leggere la domanda potrebbe aiutare :-P Lo sostituirebbe con null. In generale, l'idea sembra essere quella persona? .GetName restituisce null se person è null o person.getName in caso contrario. Quindi è praticamente come sostituire "" con null in tutti i tuoi esempi.
iscriviti il

1
Potrebbe anche essere stata lanciata una NullReferenceException getName()o getGivenName()che non saprai se restituisci semplicemente una stringa vuota per tutte le occorrenze.
Jimmy T.

1
In Java è NullPointerException.
Tuupertunut

in C #, person?.getName()?.getGivenName() ?? ""è l'equivalente del tuo primo esempio, con l'eccezione che se getGivenName()restituisce null, darà comunque""
Austin_Anderson,

0

Ecco qua, invocazione null-safe 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;
    }
}

Dovrò provarlo. SI nutre seri dubbi su tutta questa faccenda "facoltativa". Sembra un brutto trucco.
ggb667

3
L'intero scopo della proposta dell'operatore Elvis era di farlo in una riga. Questo approccio non è migliore dell'approccio "if (! = Null) di cui sopra. In effetti direi che è peggio in quanto non è semplice. Inoltre dovresti evitare di lanciare e catturare errori a causa del sovraccarico.
JustinKSU

Come ha detto @Darron in un'altra risposta, lo stesso vale qui: "Il problema con questo stile è che la NullPointerException potrebbe non provenire da dove ti aspettavi. E quindi potrebbe nascondere un vero bug."
Helder Pereira

0

Se qualcuno sta cercando un'alternativa per le vecchie versioni java, puoi provare questa che ho scritto:

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

}

0

Puoi testare il codice che hai fornito e darà un errore di sintassi, quindi non è supportato in Java. Groovy lo supporta ed è stato proposto per Java 7 (ma non è mai stato incluso).

Tuttavia, è possibile utilizzare l'Opzionale fornito in Java 8. Questo potrebbe aiutarti a ottenere qualcosa di simile. https://docs.oracle.com/javase/8/docs/api/java/util/Optional.html http://www.oracle.com/technetwork/articles/java/java8-optional-2175753.html

Codice di esempio per facoltativo


0

Poiché Android non supporta le funzioni Lambda a meno che il sistema operativo installato non sia> = 24, è necessario utilizzare la reflection.

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

-4

Se questo non è un problema di prestazioni per te, puoi scrivere

public String getFirstName(Person person) {
  try {
     return person.getName().getGivenName();
  } catch (NullPointerException ignored) {
     return null;
  }
} 

8
Il problema con questo stile è che la NullPointerException potrebbe non provenire da dove ti aspettavi. E quindi potrebbe nascondere un vero bug.
Darron

2
@ Darron, puoi fare un esempio di un getter che hai scritto che potrebbe generare un NPE e come vorresti gestirlo in modo diverso?
Peter Lawrey

2
lo si esegue in una variabile separata e lo si verifica con un if come di consueto. L'idea alla base di questo operatore è eliminare quella bruttezza. Darron ha ragione, la tua soluzione potrebbe nascondere e buttare via le eccezioni che vuoi eliminare. Come se getName()avessi lanciato internamente un'eccezione che non vuoi buttare via.
Mike Miller

2
Nessuna eccezione, dovrebbe essere una NullPointerException. Stai cercando di proteggerti da una situazione che non hai iniziato a spiegare come potrebbe verificarsi in un'applicazione reale.
Peter Lawrey

1
In un'applicazione reale Personpotrebbe esserci un proxy che accede a un DB o ad una memoria non heap e potrebbe esserci un bug da qualche parte ... Non molto realistico, ma, Peter, scommetto che non hai mai scritto un pezzo di codice come sopra .
maaartinus
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.