Evitare! = Dichiarazioni null


4015

Uso object != nullmolto per evitare NullPointerException.

C'è una buona alternativa a questo?

Ad esempio, utilizzo spesso:

if (someobject != null) {
    someobject.doCalc();
}

Questa verifica la presenza di una NullPointerExceptionper l' someobjectoggetto in questo frammento di codice.

Si noti che la risposta accettata potrebbe non essere aggiornata, consultare https://stackoverflow.com/a/2386013/12943 per un approccio più recente.


120
@Shervin Incoraggiare i null rende il codice meno comprensibile e meno affidabile.
Tom Hawtin - tackline

44
Sono stati proposti gli operatori Elvis ma sembra che non sarà in Java 7 . Peccato, ?. ?: e? [] sono incredibili risparmiatori di tempo.
Scott,

72
Non usare null è superiore alla maggior parte degli altri suggerimenti qui. Genera eccezioni, non restituire o consentire null. A proposito, la parola chiave 'assert' è inutile, perché è disabilitata per impostazione predefinita. Utilizzare un meccanismo di errore sempre abilitato
ianpojman,

13
Questo è uno dei motivi per cui sto usando Scala. In Scala tutto non è nulla. Se si desidera consentire il passaggio o la restituzione di "niente", è necessario utilizzare in modo esplicito l'opzione [T] anziché solo l'argomento T als o il tipo restituito.
Thekwasti,

11
@thSoft In effetti, il fantastico Optiontipo di Scala è rovinato dalla riluttanza della lingua a controllare o impedire null. L'ho menzionato su hackernews e sono stato sottratto e ho detto che "nessuno usa null in Scala comunque". Indovina un po ', sto già trovando null nella Scala scritta da un collega. Sì, stanno "facendo qualcosa di sbagliato" e devono essere istruiti a riguardo, ma il fatto rimane che il sistema di tipi di linguaggio dovrebbe proteggermi da questo e non lo fa :(
Andres F.

Risposte:


2644

Questo per me suona come un problema ragionevolmente comune che gli sviluppatori da junior a intermedi tendono ad affrontare ad un certo punto: o non conoscono o non si fidano dei contratti a cui partecipano e superano difensivamente i null. Inoltre, quando scrivono il proprio codice, tendono a fare affidamento sulla restituzione di valori null per indicare qualcosa che richiede quindi al chiamante di verificare la presenza di valori null.

In altre parole, ci sono due casi in cui viene visualizzato il controllo null:

  1. Dove null è una risposta valida in termini di contratto; e

  2. Dove non è una risposta valida.

(2) è facile. Utilizzare assertistruzioni (asserzioni) o consentire errori (ad esempio NullPointerException ). Le asserzioni sono una funzionalità Java molto sottoutilizzata che è stata aggiunta in 1.4. La sintassi è:

assert <condition>

o

assert <condition> : <object>

dove <condition>è un'espressione booleana ed <object>è un oggetto il cui toString()output del metodo verrà incluso nell'errore.

Un'istruzione assertgenera un Error( AssertionError) se la condizione non è vera. Per impostazione predefinita, Java ignora le asserzioni. È possibile abilitare le asserzioni passando l'opzione -eaalla JVM. È possibile abilitare e disabilitare le asserzioni per singole classi e pacchetti. Ciò significa che è possibile convalidare il codice con le asserzioni durante lo sviluppo e il test e disabilitarle in un ambiente di produzione, anche se i miei test hanno mostrato quasi nessun impatto sulle prestazioni da parte delle asserzioni.

Non usare le asserzioni in questo caso è OK perché il codice fallirà, che è ciò che accadrà se si usano le asserzioni. L'unica differenza è che con le asserzioni potrebbe accadere prima, in modo più significativo e possibilmente con informazioni aggiuntive, il che potrebbe aiutarti a capire perché è successo se non te lo aspettavi.

(1) è un po 'più difficile. Se non hai alcun controllo sul codice che stai chiamando, sei bloccato. Se null è una risposta valida, devi verificarla.

Se è il codice che controlli, tuttavia (e questo è spesso il caso), allora è una storia diversa. Evitare di utilizzare null come risposta. Con i metodi che restituiscono raccolte, è facile: restituire raccolte vuote (o matrici) invece di null praticamente sempre.

Con le non-collezioni potrebbe essere più difficile. Considera questo come esempio: se hai queste interfacce:

public interface Action {
  void doSomething();
}

public interface Parser {
  Action findAction(String userInput);
}

dove Parser accetta input utente non elaborati e trova qualcosa da fare, forse se stai implementando un'interfaccia della riga di comando per qualcosa. Ora è possibile rendere nullo il contratto se non viene eseguita alcuna azione appropriata. Questo porta al controllo nullo di cui stai parlando.

Una soluzione alternativa è non restituire mai null e utilizzare invece il modello Null Object :

public class MyParser implements Parser {
  private static Action DO_NOTHING = new Action() {
    public void doSomething() { /* do nothing */ }
  };

  public Action findAction(String userInput) {
    // ...
    if ( /* we can't find any actions */ ) {
      return DO_NOTHING;
    }
  }
}

Confrontare:

Parser parser = ParserFactory.getParser();
if (parser == null) {
  // now what?
  // this would be an example of where null isn't (or shouldn't be) a valid response
}
Action action = parser.findAction(someInput);
if (action == null) {
  // do nothing
} else {
  action.doSomething();
}

per

ParserFactory.getParser().findAction(someInput).doSomething();

che è un design molto migliore perché porta a un codice più conciso.

Detto questo, forse è del tutto appropriato per il metodo findAction () generare un'eccezione con un messaggio di errore significativo, specialmente in questo caso in cui si fa affidamento sull'input dell'utente. Sarebbe molto meglio per il metodo findAction generare un'eccezione piuttosto che per il metodo chiamante esplodere con una semplice NullPointerException senza alcuna spiegazione.

try {
    ParserFactory.getParser().findAction(someInput).doSomething();
} catch(ActionNotFoundException anfe) {
    userConsole.err(anfe.getMessage());
}

Oppure, se ritieni che il meccanismo try / catch sia troppo brutto, piuttosto che Non fare nulla, l'azione predefinita dovrebbe fornire un feedback all'utente.

public Action findAction(final String userInput) {
    /* Code to return requested Action if found */
    return new Action() {
        public void doSomething() {
            userConsole.err("Action not found: " + userInput);
        }
    }
}

631
Non sono d'accordo con le tue dichiarazioni per l'azione DO_NOTHING. Se il metodo action find non riesce a trovare un'azione, restituire null è la cosa giusta da fare. Hai "trovato" un'azione nel tuo codice che non è realmente trovata, che viola il principio del metodo, per trovare un'azione utilizzabile.
MetroidFan2002,

266
Sono d'accordo sul fatto che null sia abusato in Java, in particolare con le liste. Quindi molte API sarebbero migliori se restituissero un elenco / array / raccolta vuoto anziché null. Molte volte, viene utilizzato null in cui deve essere generata un'eccezione. Dovrebbe essere generata un'eccezione se il parser non può analizzare.
Laplie Anderson,

92
Quest'ultimo esempio è IIRC, il modello di progettazione Null Object.
Steven Evers,

62
@Cshah (e MetroidFan2002). Semplice, inseriscilo nel contratto e quindi è chiaro che un'azione restituita non trovata non farà nulla. Se si tratta di informazioni importanti per il chiamante, fornire un modo per scoprire che si trattava di un'azione non trovata (ovvero fornire un metodo per verificare se il risultato fosse l'oggetto DO_NOTHING). In alternativa, se l'azione dovrebbe essere normalmente trovata, allora non dovresti ancora restituire null ma invece lanciare un'eccezione che indica specificamente quella condizione - questo porta comunque a un codice migliore. Se lo si desidera, fornire un metodo separato che restituisca il valore booleano per verificare se esiste un'azione.
Kevin Brock,

35
Conciso non equivale a un codice di qualità. Mi dispiace che tu la pensi così. Il codice nasconde situazioni in cui un errore sarebbe vantaggioso.
gshauger,

635

Se si utilizza (o si prevede di utilizzare) un IDE Java come JetBrains IntelliJ IDEA , Eclipse o Netbeans o uno strumento come findbugs, è possibile utilizzare le annotazioni per risolvere questo problema.

Fondamentalmente, hai @Nullablee @NotNull.

È possibile utilizzare in metodo e parametri, in questo modo:

@NotNull public static String helloWorld() {
    return "Hello World";
}

o

@Nullable public static String helloWorld() {
    return "Hello World";
}

Il secondo esempio non verrà compilato (in IntelliJ IDEA).

Quando si utilizza la prima helloWorld()funzione in un altro codice:

public static void main(String[] args)
{
    String result = helloWorld();
    if(result != null) {
        System.out.println(result);
    }
}

Ora il compilatore IDEA IntelliJ ti dirà che il controllo è inutile, poiché la helloWorld()funzione non tornerà nullmai.

Utilizzando il parametro

void someMethod(@NotNull someParameter) { }

se scrivi qualcosa del tipo:

someMethod(null);

Questo non verrà compilato.

Ultimo esempio usando @Nullable

@Nullable iWantToDestroyEverything() { return null; }

Facendo questo

iWantToDestroyEverything().something();

E puoi essere sicuro che ciò non accadrà. :)

È un buon modo per consentire al compilatore di controllare qualcosa di più di quanto non faccia normalmente e per far rispettare i contratti. Sfortunatamente, non è supportato da tutti i compilatori.

In IntelliJ IDEA 10.5 e versioni successive, hanno aggiunto il supporto per qualsiasi altra @Nullable @NotNullimplementazione.

Vedi post sul blog Annotazioni @ Nullable / @ NotNull più flessibili e configurabili .


120
@NotNull, @NullableE altre annotazioni nullness fanno parte di JSR 305 . Puoi anche usarli per rilevare potenziali problemi con strumenti come FindBugs .
Jacek S,

32
Trovo stranamente fastidioso che le interfacce @NotNull& @Nullablevivano nel pacchetto com.sun.istack.internal. (Immagino di associare com.sun ad avvertimenti sull'uso di un'API proprietaria.)
Jonik,

20
la portabilità del codice va a vuoto con jetbrains. Ci penserei due volte (quadrata) prima di legarmi al livello ide.
Java Ka Baby,

10
Non credo davvero che l'uso di un compilatore personalizzato sia una soluzione praticabile a questo problema.
Drago di Shivan,

64
La cosa buona delle annotazioni, che @NotNulle @Nullablesono, è che si degradano piacevolmente quando il codice sorgente è costruito da un sistema che non le comprende. Quindi, in effetti, l'argomento secondo cui il codice non è portatile potrebbe non essere valido: se si utilizza un sistema che supporta e comprende queste annotazioni, si ottiene il vantaggio aggiuntivo di un controllo degli errori più rigoroso, altrimenti si ottiene di meno ma il codice dovrebbe comunque costruire bene, e la qualità del tuo programma in esecuzione è LO STESSO, perché queste annotazioni non sono state comunque applicate in fase di esecuzione. Inoltre, tutti i compilatori sono personalizzati ;-)
am

321

Se i valori null non sono ammessi

Se il tuo metodo viene chiamato esternamente, inizia con qualcosa del genere:

public void method(Object object) {
  if (object == null) {
    throw new IllegalArgumentException("...");
  }

Quindi, nel resto di quel metodo, saprai che objectnon è null.

Se è un metodo interno (non parte di un'API), basta documentare che non può essere nullo e basta.

Esempio:

public String getFirst3Chars(String text) {
  return text.subString(0, 3);
}

Tuttavia, se il tuo metodo passa semplicemente il valore e il metodo successivo lo passa ecc., Potrebbe diventare problematico. In tal caso, potresti voler controllare l'argomento come sopra.

Se è consentito null

Questo dipende davvero. Se scopri che spesso faccio qualcosa del genere:

if (object == null) {
  // something
} else {
  // something else
}

Quindi mi dirigo e faccio due cose completamente diverse. Non esiste uno snippet di codice brutto, perché ho davvero bisogno di fare due cose diverse a seconda dei dati. Ad esempio, dovrei lavorare sull'input o dovrei calcolare un buon valore predefinito?


In realtà è raro per me usare il linguaggio " if (object != null && ...".

Potrebbe essere più semplice fornirti degli esempi, se mostri esempi di dove in genere usi il linguaggio.


94
Qual è il punto nel lanciare IllegalArgumentException? Penso che NullPointerException sarebbe più chiara e ciò verrebbe lanciato anche se non esegui tu stesso il controllo null. O userei assert o niente del tutto.
Axel,

23
È improbabile che qualsiasi altro valore diverso da null sia accettabile. Potresti avere IllegalArgumentException, OutOfRageException ecc ecc. A volte questo ha senso. Altre volte finisci per creare molte classi di eccezioni che non aggiungono alcun valore, quindi usi semplicemente IllegalArgumentException. Non ha senso avere un'eccezione per l'input null e un'altra per tutto il resto.
myplacedk,

7
Sì, accetto il principio fail-fast, ma nell'esempio sopra riportato, il valore non viene trasmesso, ma è l'oggetto su cui deve essere chiamato un metodo. Quindi fallisce altrettanto velocemente, e l'aggiunta di un controllo null solo per lanciare un'eccezione che verrebbe comunque generata allo stesso tempo e luogo non sembra facilitare il debug.
Axel,

8
Una falla di sicurezza? Il JDK è pieno di codice come questo. Se non vuoi che l'utente veda stacktraces, disabilitalo e basta. Nessuno ha sottinteso che il comportamento non è documentato. MySQL è scritto in C in cui la dereferenziazione dei puntatori null è un comportamento indefinito, niente come lanciare un'eccezione.
fgb,

8
throw new IllegalArgumentException("object==null")
Thorbjørn Ravn Andersen,

238

Wow, quasi odio aggiungere un'altra risposta quando abbiamo 57 modi diversi di raccomandare il NullObject pattern, ma penso che alcune persone interessate a questa domanda potrebbero sapere che esiste una proposta sul tavolo per Java 7 che aggiunge "null-safe gestione ": una sintassi semplificata per la logica if-not-equal-null.

L'esempio fornito da Alex Miller è simile al seguente:

public String getPostcode(Person person) {  
  return person?.getAddress()?.getPostcode();  
}  

Il ?.mezzo rimuove il riferimento all'identificatore sinistro solo se non è nullo, altrimenti valuta il resto dell'espressione come null. Alcune persone, come Dick Wall, membro di Java Posse e gli elettori di Devoxx adorano davvero questa proposta, ma c'è anche un'opposizione, in quanto incoraggerà effettivamente un maggiore utilizzo nullcome valore sentinella.


Aggiornamento: una proposta ufficiale per un operatore null-safe in Java 7 è stata presentata sotto Project Coin. La sintassi è leggermente diversa dall'esempio sopra, ma è la stessa nozione.


Aggiornamento: la proposta dell'operatore null-safe non è diventata Project Coin. Quindi, non vedrai questa sintassi in Java 7.


20
Penso che sia sbagliato. Dovrebbe esserci un modo per specificare che una determinata variabile è SEMPRE non nulla.
Thorbjørn Ravn Andersen,

5
Aggiornamento: la proposta non renderà Java7. Vedi blogs.sun.com/darcy/entry/project_coin_final_five .
Boris Terzic,

9
Idea interessante ma la scelta della sintassi è assurda; Non voglio una base di codice piena di punti interrogativi appuntati in ogni giunto.
Rob,

7
Questo operatore esiste in Groovy , quindi quelli che vogliono usarlo lo hanno ancora come opzione.
Muhd,

8
Questa è l'idea più geniale che abbia mai visto. Dovrebbe essere aggiunto a ogni linguaggio sensibile della sintassi C. Preferirei "appuntare punti interrogativi" dappertutto che scorrere schermate di righe o schivare "clausole di guardia" tutto il giorno.
Victor,

196

Se valori non definiti non sono consentiti:

È possibile configurare il proprio IDE per avvisare l'utente del potenziale dereferenziamento nullo. Ad esempio in Eclipse, vedi Preferenze> Java> Compilatore> Errori / Avvertenze / Analisi nulla .

Se sono consentiti valori non definiti:

Se si desidera definire una nuova API in cui i valori non definiti hanno senso , utilizzare il modello di opzione (potrebbe essere familiare dai linguaggi funzionali). Ha i seguenti vantaggi:

  • Viene indicato esplicitamente nell'API se esiste o meno un input o un output.
  • Il compilatore ti costringe a gestire il caso "non definito".
  • L'opzione è una monade , quindi non è necessario un controllo nullo dettagliato, basta usare map / foreach / getOrElse o un combinatore simile per usare in sicurezza il valore (esempio) .

Java 8 ha una Optionalclasse integrata (consigliata); per le versioni precedenti, non ci sono alternative di libreria, ad esempio Guava 's Optionalo FunctionalJava ' s Option. Ma come molti modelli di stile funzionale, l'utilizzo di Option in Java (anche 8) porta a un po 'di plateplate, che è possibile ridurre usando un linguaggio JVM meno prolisso, ad esempio Scala o Xtend.

Se hai a che fare con un'API che potrebbe restituire valori null , non puoi fare molto in Java. Xtend e Groovy hanno l' operatore Elvis ?: e l' operatore di dereference null-safe ?. , ma si noti che questo restituisce null in caso di riferimento null, quindi "difende" la corretta gestione di null.


22
In effetti, il modello Option è fantastico. Esistono alcuni equivalenti Java. Guava contiene una versione limitata di questo chiamato Opzionale che tralascia la maggior parte delle cose funzionali. In Haskell, questo modello si chiama Forse.
Ben Hardy,

9
Una classe opzionale sarà disponibile in Java 8
Pierre Henry il

1
... e non ha (ancora) mappa né flatMap: download.java.net/jdk8/docs/api/java/util/Optional.html
thSoft

3
Il modello opzionale non risolve nulla; invece di un oggetto potenzialmente nullo, ora ne hai due.
Boann

1
@Boann, se usato con cura, risolvi tutti i tuoi problemi di NPE. In caso contrario, suppongo che ci sia un problema di "utilizzo".
Louis F.

189

Solo per questa situazione -

Non verificare se una variabile è nulla prima di invocare un metodo uguale (di seguito un esempio di confronto di stringhe):

if ( foo.equals("bar") ) {
 // ...
}

comporterà un NullPointerExceptionse foonon esiste.

Puoi evitarlo confrontando le tue in Stringquesto modo:

if ( "bar".equals(foo) ) {
 // ...
}

42
Sono d'accordo - solo in quella situazione. Non sopporto i programmatori che hanno portato questo al prossimo livello superfluo e scrivono se (null! = MyVar) ... mi sembra brutto e non serve a nulla!
Alex Worden,

20
Questo è un esempio particolare, probabilmente il più usato, di una buona pratica generale: se lo conosci, fallo sempre <object that you know that is not null>.equals(<object that might be null>);. Funziona con altri metodi diversi da quelli equalsche conosci il contratto e questi metodi possono gestire i nullparametri.
Stef

9
Questo è il primo esempio che ho visto delle condizioni di Yoda che ha davvero senso
Erin Drummond,

6
NullPointerExceptions vengono generate per un motivo. Vengono lanciati perché un oggetto è nullo dove non dovrebbe essere. È compito dei programmatori risolvere questo problema, non NASCONDERE il problema.
Oliver Watkins,

1
Try-Catch-DoNiente nasconde il problema, questa è una pratica valida per aggirare lo zucchero mancante nella lingua.
echox,

167

Con Java 8 arriva la nuova java.util.Optionalclasse che probabilmente risolve parte del problema. Si può almeno dire che migliora la leggibilità del codice e nel caso delle API pubbliche rendono più chiaro il contratto dell'API allo sviluppatore del client.

Funzionano così:

Un oggetto facoltativo per un determinato tipo ( Fruit) viene creato come tipo restituito di un metodo. Può essere vuoto o contenere un Fruitoggetto:

public static Optional<Fruit> find(String name, List<Fruit> fruits) {
   for (Fruit fruit : fruits) {
      if (fruit.getName().equals(name)) {
         return Optional.of(fruit);
      }
   }
   return Optional.empty();
}

Ora guarda questo codice dove cerchiamo un elenco di Fruit( fruits) per una determinata istanza di Fruit:

Optional<Fruit> found = find("lemon", fruits);
if (found.isPresent()) {
   Fruit fruit = found.get();
   String name = fruit.getName();
}

È possibile utilizzare l' map()operatore per eseguire un calcolo su - o estrarre un valore da - un oggetto opzionale. orElse()consente di fornire un fallback per i valori mancanti.

String nameOrNull = find("lemon", fruits)
    .map(f -> f.getName())
    .orElse("empty-name");

Ovviamente, il controllo per valore nullo / vuoto è ancora necessario, ma almeno lo sviluppatore è consapevole che il valore potrebbe essere vuoto e il rischio di dimenticare di controllare è limitato.

In un'API creata da zero utilizzando Optionalogni volta che un valore restituito potrebbe essere vuoto e restituendo un oggetto semplice solo quando non può essere null(convenzione), il codice client potrebbe abbandonare i controlli null su valori restituiti oggetto semplice ...

Naturalmente Optionalpotrebbe anche essere usato come argomento del metodo, forse un modo migliore per indicare argomenti opzionali rispetto a 5 o 10 metodi di sovraccarico in alcuni casi.

Optionaloffre altri metodi convenienti, come quello orElseche consente l'uso di un valore predefinito e ifPresentche funziona con le espressioni lambda .

Vi invito a leggere questo articolo (la mia fonte principale per scrivere questa risposta) in cui la soluzione NullPointerException(e in generale il puntatore nullo) problematica e la (parziale) soluzione proposta Optionalsono ben spiegate: Java Optional Objects .


11
La guaiava di Google ha un'implementazione opzionale per Java 6+.
Bradley Gottfried,

13
È molto importante sottolineare che l'uso di Opzionale solo con ifPresent () non aggiunge molto valore al di sopra del normale controllo null. Il suo valore fondamentale è che è una monade che può essere utilizzata nelle catene di funzioni di map / flapMap, che ottiene risultati simili all'operatore Elvis in Groovy menzionato altrove. Anche senza questo utilizzo, però, trovo molto utile anche la sintassi orElse / oElseThrow.
Cornel Masson,

Questo blog ha una buona entrata sul opzionale winterbe.com/posts/2015/03/15/avoid-null-checks-in-java
JohnC

4
Perché le persone hanno la tendenza a fare questo if(optional.isPresent()){ optional.get(); }invece dioptional.ifPresent(o -> { ...})
Satyendra Kumar

1
Quindi, oltre ai suggerimenti contrattuali sulle API, si tratta in realtà solo di soddisfare i programmatori funzionali che amano incatenare i metodi all'infinito.
schiaccia

125

A seconda del tipo di oggetti che stai controllando potresti essere in grado di usare alcune delle classi nei beni comuni di Apache come: apache commons lang e apache commons collection

Esempio:

String foo;
...
if( StringUtils.isBlank( foo ) ) {
   ///do something
}

oppure (a seconda di cosa è necessario controllare):

String foo;
...
if( StringUtils.isEmpty( foo ) ) {
   ///do something
}

La classe StringUtils è solo una delle tante; ci sono alcune buone classi nei beni comuni che manipolano in modo sicuro.

Ecco un esempio di come è possibile utilizzare la null null in JAVA quando si include la libreria apache (commons-lang-2.4.jar)

public DOCUMENT read(String xml, ValidationEventHandler validationEventHandler) {
    Validate.notNull(validationEventHandler,"ValidationHandler not Injected");
    return read(new StringReader(xml), true, validationEventHandler);
}

E se stai usando Spring, Spring ha anche la stessa funzionalità nel suo pacchetto, vedi library (spring-2.4.6.jar)

Esempio su come utilizzare questa classe statica dalla primavera (org.springframework.util.Assert)

Assert.notNull(validationEventHandler,"ValidationHandler not Injected");

5
Inoltre puoi usare la versione più generica di Apache Commons, molto utile all'inizio dei metodi per controllare i parametri che trovo. Validate.notNull (object, "object non deve essere null"); commons.apache.org/lang/apidocs/org/apache/commons/lang/…
monojohnny

@monojohnny Validate utilizza le istruzioni Assert in ?. lo chiedo perché Assert può essere attivato / disattivato su JVM ed è consigliabile non utilizzarlo in produzione.
Kurapika,

Non credo - credo che
generi

96
  • Se consideri un oggetto non dovrebbe essere nullo (o è un bug) usa un'asserzione.
  • Se il tuo metodo non accetta parametri null, dillo nel javadoc e usa un'asserzione.

Devi controllare l'oggetto! = Null solo se vuoi gestire il caso in cui l'oggetto potrebbe essere null ...

C'è una proposta per aggiungere nuove annotazioni in Java7 per aiutare con i parametri null / notnull: http://tech.puredanger.com/java7/#jsr308



91

Sono un fan del codice "fail fast". Chiediti: stai facendo qualcosa di utile nel caso in cui il parametro sia nullo? Se non hai una risposta chiara a ciò che il tuo codice dovrebbe fare in quel caso ... Vale a dire che non dovrebbe mai essere nullo in primo luogo, quindi ignoralo e lascia che venga generata una NullPointerException. Il codice chiamante avrà lo stesso senso di un NPE come se fosse un IllegalArgumentException, ma sarà più facile per lo sviluppatore eseguire il debug e capire cosa è andato storto se viene lanciato un NPE anziché il codice che tenta di eseguire qualche altra imprevista imprevista logica - che alla fine porta comunque al fallimento dell'applicazione.


2
meglio usare asserzioni, ovvero Contract.notNull (abc, "abc deve essere non nullo, non è stato possibile caricare durante xyz?"); - questo è un modo più compatto rispetto a fare un if (abc! = null) {lanciare nuova RuntimeException ...}
ianpojman,

76

Il framework delle raccolte di Google offre un modo valido ed elegante per ottenere il controllo null.

Esiste un metodo in una classe di libreria come questo:

static <T> T checkNotNull(T e) {
   if (e == null) {
      throw new NullPointerException();
   }
   return e;
}

E l'uso è (con import static):

...
void foo(int a, Person p) {
   if (checkNotNull(p).getAge() > a) {
      ...
   }
   else {
      ...
   }
}
...

O nel tuo esempio:

checkNotNull(someobject).doCalc();

71
mmm, qual è la differenza? p.getAge () genererebbe lo stesso NPE con un sovraccarico minore e una traccia dello stack più chiara. Cosa mi sto perdendo?
misterioso

15
È meglio generare un'espressione IllegalArgumentException ("e == null") nel tuo esempio in quanto indica chiaramente che si tratta di un'eccezione destinata al programmatore (insieme a informazioni sufficienti per consentire effettivamente al manutentore di identificare il problema). NullPointerExceptions dovrebbe essere riservato alla JVM, poiché indica chiaramente che ciò non era intenzionale (e di solito accade in un posto difficile da identificare)
Thorbjørn Ravn Andersen,

6
Questo fa ora parte di Google Guava.
Steven Benitez,

32
Odora di ingegneria eccessiva per me. Lascia che JVM lanci un NPE e non ingombrare il tuo codice con questa spazzatura.
Alex Worden,

5
Mi piace e apro la maggior parte dei metodi e costruttori con controlli espliciti sugli argomenti; se c'è un errore, i metodi falliscono sempre nelle prime righe e conosco il riferimento offensivo senza trovare qualcosa di similegetThing().getItsThing().getOtherThing().wowEncapsulationIsBroken().setLol("hi");
Cory Kendall

75

A volte, hai metodi che operano sui suoi parametri che definiscono un'operazione simmetrica:

a.f(b); <-> b.f(a);

Se sai che b non può mai essere nullo, puoi semplicemente scambiarlo. È più utile per uguali: invece di foo.equals("bar");fare meglio "bar".equals(foo);.


2
Ma poi devi assumere equals(potrebbe essere qualsiasi metodo) gestirà null correttamente. Davvero tutto ciò che sta facendo è passare la responsabilità a qualcun altro (o un altro metodo).
Supericy,

4
@Supericy Fondamentalmente sì, ma equals(o qualunque metodo) deve nullcomunque verificare . O dichiarare esplicitamente che non lo è.
Angelo Fuchs,

74

Piuttosto che Null Object Pattern - che ha i suoi usi - potresti considerare situazioni in cui l'oggetto null è un bug.

Quando viene generata l'eccezione, esaminare la traccia dello stack e risolvere il problema.


17
Il problema è che di solito perdi il contesto poiché NullPointerException non indica QUALE variabile era nulla e potresti avere diverse "." - operazioni sulla linea. L'uso di "if (foo == null) genera una nuova RuntimeException (" foo == null ")" ti consente di indicare esplicitamente COSA era sbagliato, dando al tuo stack traccia molto più valore per coloro che devono ripararlo.
Thorbjørn Ravn Andersen,

1
Con Andersen, mi piacerebbe che il sistema di eccezioni Java fosse in grado di includere il nome di una variabile su cui si sta lavorando, in modo che NullPointerExceptions indicasse non solo la linea su cui si era verificata l'eccezione, ma anche il nome della variabile. Questo dovrebbe funzionare bene nel software non offuscato.
fwielstra,

Non sono ancora riuscito a farlo funzionare, ma questo ha lo scopo di risolvere esattamente quel problema.
MatrixFrog,

Qual è il problema? Basta prendere l'NPE al livello appropriato dove è disponibile un contesto sufficiente, scaricare le informazioni sul contesto e riproporre l'eccezione ... è così facile con Java.
user1050755

3
Avevo un professore che predicava contro il concatenamento delle chiamate di metodo. La sua teoria era che dovresti stare attento alle catene di chiamate che erano più lunghe di 2 metodi. Non so se sia una regola difficile, ma rimuove definitivamente la maggior parte dei problemi con le tracce dello stack NPE.
RustyTheBoyRobot

74

Null non è un "problema". È parte integrante di un set completo di strumenti di modellazione. Il software mira a modellare la complessità del mondo e null sopporta il suo peso. Null indica "Nessun dato" o "Sconosciuto" in Java e simili. Quindi è appropriato usare null per questi scopi. Non preferisco il modello "Null object"; Penso che sorga il problema di " chi custodirà i guardiani ".
Se mi chiedi come si chiama la mia ragazza ti dirò che non ho una ragazza. Nel linguaggio Java restituirò null. Un'alternativa sarebbe quella di gettare un'eccezione significativa per indicare qualche problema che non può essere (o non

  1. Per una "domanda sconosciuta", fornire "risposta sconosciuta". (Essere null-safe dove questo è corretto dal punto di vista aziendale) Il controllo degli argomenti per null una volta all'interno di un metodo prima dell'utilizzo allevia più chiamanti dal controllarli prima di una chiamata.

    public Photo getPhotoOfThePerson(Person person) {
        if (person == null)
            return null;
        // Grabbing some resources or intensive calculation
        // using person object anyhow.
    }

    Il precedente portava al normale flusso logico per non ottenere foto di una ragazza inesistente dalla mia libreria di foto.

    getPhotoOfThePerson(me.getGirlfriend())

    E si adatta alla nuova API Java in arrivo (in attesa)

    getPhotoByName(me.getGirlfriend()?.getName())

    Mentre è piuttosto 'normale flusso di affari' non trovare foto archiviate nel DB per alcune persone, in alcuni casi usavo le coppie come sotto

    public static MyEnum parseMyEnum(String value); // throws IllegalArgumentException
    public static MyEnum parseMyEnumOrNull(String value);

    E non detestare di digitare <alt> + <shift> + <j>(generare javadoc in Eclipse) e scrivere tre parole aggiuntive per l'API pubblica. Questo sarà più che sufficiente per tutti tranne quelli che non leggono la documentazione.

    /**
     * @return photo or null
     */

    o

    /**
     * @return photo, never null
     */
  2. Questo è un caso piuttosto teorico e nella maggior parte dei casi dovresti preferire l'API java null safe (nel caso in cui verrà rilasciato tra altri 10 anni), ma NullPointerExceptionè una sottoclasse di un Exception. Quindi è una forma Throwableche indica le condizioni che un'applicazione ragionevole potrebbe voler catturare ( javadoc )! Per utilizzare il primo maggior vantaggio di eccezioni e separare il codice di gestione degli errori dal codice "normale" ( secondo i creatori di Java ) è opportuno, per quanto mi riguarda, catturare NullPointerException.

    public Photo getGirlfriendPhoto() {
        try {
            return appContext.getPhotoDataSource().getPhotoByName(me.getGirlfriend().getName());
        } catch (NullPointerException e) {
            return null;
        }
    }

    Potrebbero sorgere domande:

    D. Cosa succede se getPhotoDataSource()restituisce null?
    A. Dipende dalla logica aziendale. Se non riesco a trovare un album fotografico, non ti mostrerò foto. Cosa succede se appContext non è inizializzato? La logica aziendale di questo metodo lo sopporta. Se la stessa logica deve essere più rigorosa, quindi lanciare un'eccezione fa parte della logica aziendale e deve essere utilizzato il controllo esplicito per null (caso 3). La nuova API Java Null-safe si adatta meglio qui per specificare in modo selettivo cosa implica e cosa non implica essere inizializzato per essere fail-fast in caso di errori del programmatore.

    D. Il codice ridondante potrebbe essere eseguito e le risorse non necessarie potrebbero essere catturate.
    R. Potrebbe avvenire se getPhotoByName()provasse ad aprire una connessione al database, a creare PreparedStatemente utilizzare il nome della persona come parametro SQL alla fine. L'approccio per una domanda sconosciuta dà una risposta sconosciuta (caso 1) funziona qui. Prima di afferrare le risorse il metodo dovrebbe controllare i parametri e restituire risultati "sconosciuti" se necessario.

    D. Questo approccio ha una penalità prestazionale a causa dell'apertura della chiusura di prova.
    A. Il software dovrebbe essere facile da capire e modificare in primo luogo. Solo dopo questo, si potrebbe pensare alle prestazioni e solo se necessario! e dove necessario! ( fonte ) e molti altri).

    PS. Questo approccio sarà ragionevole da usare come il codice di gestione degli errori separato dal principio del codice "normale" è ragionevole da usare in qualche luogo. Considera il prossimo esempio:

    public SomeValue calculateSomeValueUsingSophisticatedLogic(Predicate predicate) {
        try {
            Result1 result1 = performSomeCalculation(predicate);
            Result2 result2 = performSomeOtherCalculation(result1.getSomeProperty());
            Result3 result3 = performThirdCalculation(result2.getSomeProperty());
            Result4 result4 = performLastCalculation(result3.getSomeProperty());
            return result4.getSomeProperty();
        } catch (NullPointerException e) {
            return null;
        }
    }
    
    public SomeValue calculateSomeValueUsingSophisticatedLogic(Predicate predicate) {
        SomeValue result = null;
        if (predicate != null) {
            Result1 result1 = performSomeCalculation(predicate);
            if (result1 != null && result1.getSomeProperty() != null) {
                Result2 result2 = performSomeOtherCalculation(result1.getSomeProperty());
                if (result2 != null && result2.getSomeProperty() != null) {
                    Result3 result3 = performThirdCalculation(result2.getSomeProperty());
                    if (result3 != null && result3.getSomeProperty() != null) {
                        Result4 result4 = performLastCalculation(result3.getSomeProperty());
                        if (result4 != null) {
                            result = result4.getSomeProperty();
                        }
                    }
                }
            }
        }
        return result;
    }

    PPS. Per coloro che hanno una rapida votazione (e non così veloce da leggere la documentazione), vorrei dire che non ho mai preso un'eccezione null-pointer (NPE) in vita mia. Ma questa possibilità è stata intenzionalmente progettata dai creatori di Java perché NPE è una sottoclasse di Exception. Abbiamo un precedente nella storia Java quando ThreadDeathè un Errornon perché è in realtà un errore di applicazione, ma solo perché non era destinato a essere catturati! Quanto NPE si adatta ad essere un Errordi ThreadDeath! Ma non è.

  3. Controlla "Nessun dato" solo se la logica aziendale lo implica.

    public void updatePersonPhoneNumber(Long personId, String phoneNumber) {
        if (personId == null)
            return;
        DataSource dataSource = appContext.getStuffDataSource();
        Person person = dataSource.getPersonById(personId);
        if (person != null) {
            person.setPhoneNumber(phoneNumber);
            dataSource.updatePerson(person);
        } else {
            Person = new Person(personId);
            person.setPhoneNumber(phoneNumber);
            dataSource.insertPerson(person);
        }
    }

    e

    public void updatePersonPhoneNumber(Long personId, String phoneNumber) {
        if (personId == null)
            return;
        DataSource dataSource = appContext.getStuffDataSource();
        Person person = dataSource.getPersonById(personId);
        if (person == null)
            throw new SomeReasonableUserException("What are you thinking about ???");
        person.setPhoneNumber(phoneNumber);
        dataSource.updatePerson(person);
    }

    Se appContext o dataSource non sono inizializzati, il runtime non gestito NullPointerException ucciderà il thread corrente e verrà elaborato da Thread.defaultUncaughtExceptionHandler (per poter definire e utilizzare il proprio logger preferito o altro meccanismo di notifica). Se non impostato, ThreadGroup # uncaughtException stamperà stacktrace su err di sistema. Si dovrebbe monitorare il registro degli errori dell'applicazione e aprire il problema di Jira per ogni eccezione non gestita che in realtà è un errore dell'applicazione. Il programmatore dovrebbe correggere bug da qualche parte nelle cose di inizializzazione.


6
La cattura NullPointerExceptione il ritorno nullè orribile per il debug. Alla fine si finisce con NPE in seguito, ed è davvero difficile capire cosa fosse originariamente nullo.
artbristol

23
Sottovaluterei se avessi la reputazione. Non solo è nullo non necessario, è un buco nel sistema dei tipi. L'assegnazione di un albero a un elenco è un errore di tipo perché gli alberi non sono valori di tipo Elenco; secondo la stessa logica, l'assegnazione di null dovrebbe essere un errore di tipo perché null non è un valore di tipo Object o qualsiasi tipo utile per quella materia. Persino l'uomo che ha inventato il nulla lo considera il suo "errore da miliardi di dollari". La nozione di "un valore che potrebbe essere un valore di tipo T O niente" è il suo tipo e dovrebbe essere rappresentata come tale (ad es. Forse <T> o Opzionale <T>).
Doval,

2
A partire da "Forse <T> o Opzionale <T>" hai ancora bisogno di scrivere codice in if (maybeNull.hasValue()) {...}modo che cosa fa la differenza if (maybeNull != null)) {...}?
Mykhaylo Adamovych,

1
Al momento di "catturare NullPointerException e restituire null è orribile da eseguire il debug. Alla fine si finisce con NPE in seguito, ed è davvero difficile capire cosa fosse originariamente nullo". Sono totalmente d'accordo! In questi casi è necessario scrivere una dozzina di istruzioni "if" o lanciare NPE se la logica aziendale implica dati in atto o utilizzare un operatore null-safe dal nuovo Java. Ma ci sono casi in cui non mi interessa quale passo esatto mi dia nulla. Ad esempio, il calcolo di alcuni valori per l'utente appena prima di essere visualizzati sullo schermo quando si prevede che i dati potrebbero essere mancanti.
Mykhaylo Adamovych,

3
@MykhayloAdamovych: il vantaggio di Maybe<T>o Optional<T>non è nel caso in cui il tuo Tpotrebbe essere nullo, ma nel caso in cui non dovrebbe mai essere nullo. Se hai un tipo che significa esplicitamente "questo valore potrebbe essere nullo - usa con cautela", e usi e restituisci tale tipo in modo coerente, quindi ogni volta che vedi un semplice vecchio Tnel tuo codice, puoi presumere che non sia mai nullo. (Certo, questo sarebbe molto più utile se applicabile dal compilatore.)
cHao,

71

Java 7 ha una nuova java.util.Objectsclasse di utilità su cui esiste un requireNonNull()metodo. Tutto ciò che fa è lanciare a NullPointerExceptionse il suo argomento è null, ma pulisce un po 'il codice. Esempio:

Objects.requireNonNull(someObject);
someObject.doCalc();

Il metodo è più utile per verificare poco prima di un'assegnazione in un costruttore, in cui ogni suo utilizzo può salvare tre righe di codice:

Parent(Child child) {
   if (child == null) {
      throw new NullPointerException("child");
   }
   this.child = child;
}

diventa

Parent(Child child) {
   this.child = Objects.requireNonNull(child, "child");
}

9
In realtà, il tuo esempio costituisce un eccesso di codice: la prima riga è superflua perché l'NPE verrebbe lanciato nella seconda riga. ;-)
user1050755

1
Vero. Un esempio migliore sarebbe se la seconda riga fosse doCalc(someObject).
Stuart segna il

Dipende. Se sei l'autore di doCalc (), suggerirei di inserire il segno di spunta nel corpo di quel metodo (se possibile). E quindi molto probabilmente chiamerai someObject.someMethod () dove di nuovo non è necessario controllare null. :-)
user1050755

Bene, se non sei l'autore di doCalc(), e non lancia immediatamente NPE quando viene dato null, dovresti controllare null e lanciare NPE tu stesso. Questo è ciò che Objects.requireNonNull()serve.
Stuart segna il

7
Non è solo un eccesso di codice. Meglio controllare in anticipo che a metà strada attraverso un metodo che causa effetti collaterali o usa il tempo / lo spazio.
Rob Grant,

51

In definitiva, l'unico modo per risolvere completamente questo problema è usare un linguaggio di programmazione diverso:

  • In Objective-C, puoi fare l'equivalente di invocare un metodo nile non accadrà assolutamente nulla. Ciò rende superflui i controlli null, ma può rendere gli errori molto più difficili da diagnosticare.
  • A Nizza , un linguaggio derivato da Java, esistono due versioni di tutti i tipi: una versione potenzialmente nulla e una versione non nulla. È possibile richiamare metodi solo su tipi non nulli. I tipi potenzialmente nulli possono essere convertiti in tipi non nulli mediante controllo esplicito per null. Ciò rende molto più semplice sapere dove sono necessari controlli null e dove non lo sono.

Woah ... La risposta più corretta e viene annullata, dov'è la giustizia? In Java null è sempre un valore valido. È il corollario di Tutto è un oggetto - Null è un Tutto (ovviamente ignoriamo i primitivi qui ma tu hai l'idea). Personalmente preferisco l'approccio adottato da Nice, anche se potremmo far sì che i metodi possano essere invocati su tipi nullable e promuovere le NPE a eccezioni verificate. Questo dovrebbe essere fatto tramite uno switch del compilatore, dato che infrange tutto il codice esistente :(
CurtainDog

4
Non ho familiarità con Nice, ma Kotlin implementa la stessa idea, ha tipi nulli e non nulli integrati nel sistema di tipi del linguaggio. Molto più conciso di Optionals o modello di oggetti null.
mtsahakis,

38

"Problema" comune in Java davvero.

Innanzitutto, i miei pensieri su questo:

Ritengo che sia male "mangiare" qualcosa quando viene passato NULL in cui NULL non è un valore valido. Se non esci dal metodo con una sorta di errore, significa che nulla è andato storto nel tuo metodo, il che non è vero. Quindi probabilmente restituisci null in questo caso, e nel metodo di ricezione controlli di nuovo null, e non finisce mai, e finisci con "if! = Null", ecc.

Quindi, IMHO, null deve essere un errore critico che impedisce ulteriori esecuzioni (ovvero, dove null non è un valore valido).

Il modo in cui risolvo questo problema è questo:

Innanzitutto, seguo questa convenzione:

  1. Tutti i metodi / API pubblici controllano sempre i suoi argomenti per null
  2. Tutti i metodi privati ​​non controllano la presenza di null poiché sono metodi controllati (basta lasciar morire con eccezione nullpointer nel caso in cui non sia stato gestito sopra)
  3. Gli unici altri metodi che non verificano la presenza di null sono i metodi di utilità. Sono pubblici, ma se li chiami per qualche motivo, sai quali parametri passi. È come cercare di far bollire l'acqua nel bollitore senza fornire acqua ...

E infine, nel codice, la prima riga del metodo pubblico va così:

ValidationUtils.getNullValidator().addParam(plans, "plans").addParam(persons, "persons").validate();

Si noti che addParam () restituisce self, quindi è possibile aggiungere più parametri da verificare.

Il metodo validate()verrà verificato ValidationExceptionse uno qualsiasi dei parametri è null (selezionato o deselezionato è più un problema di progettazione / gusto, ma il mio ValidationExceptionè selezionato).

void validate() throws ValidationException;

Il messaggio conterrà il testo seguente se, ad esempio, "piani" è nullo:

" Rilevato valore dell'argomento non valido null per il parametro [piani] "

Come puoi vedere, il secondo valore nel metodo addParam () (stringa) è necessario per il messaggio utente, perché non puoi facilmente rilevare il nome della variabile passata, anche con la riflessione (comunque non oggetto di questo post ...).

E sì, sappiamo che oltre questa linea non incontreremo più un valore nullo, quindi invocheremo metodi in modo sicuro su quegli oggetti.

In questo modo, il codice è pulito, facilmente gestibile e leggibile.


3
Assolutamente. Le applicazioni che generano solo errori e arresti anomali sono di qualità superiore perché non vi sono dubbi quando non funzionano. Le applicazioni che inghiottono gli errori nella migliore delle ipotesi si degradano con garbo, ma di solito non funzionano in modo difficile da notare e non vengono corretti. E quando si nota il problema, è molto più difficile eseguire il debug.
Paul Jackson,

35

Fare questa domanda sottolinea che potresti essere interessato alle strategie di gestione degli errori. L'architetto del tuo team dovrebbe decidere come risolvere gli errori. Esistono diversi modi per farlo:

  1. consentire alle eccezioni di passare attraverso - catturarle nel "ciclo principale" o in qualche altra routine di gestione.

    • controllare le condizioni di errore e gestirle in modo appropriato

Sicuramente dai un'occhiata anche alla programmazione orientata agli aspetti: hanno dei modi ben precisi da inserire if( o == null ) handleNull()nel tuo bytecode.


35

Oltre a utilizzare assertè possibile utilizzare quanto segue:

if (someobject == null) {
    // Handle null here then move on.
}

Questo è leggermente meglio di:

if (someobject != null) {
    .....
    .....



    .....
}

2
Mh, perché quello? Per favore, non sentirti difensivo, vorrei solo saperne di più su Java :)
Matthias Meid,

9
@Mudu Come regola generale, preferisco che l'espressione in un'istruzione if sia un'istruzione più "positiva", piuttosto che "negativa". Quindi, se vedessi, if (!something) { x(); } else { y(); }sarei propenso a riformularlo come if (something) { y(); } else { x(); }(anche se si potrebbe sostenere che != nullè l'opzione più positiva ...). Ma soprattutto, la parte importante del codice non è racchiusa in {}se hai un livello di rientro in meno per la maggior parte del metodo. Non so se quello fosse il ragionamento di fastcodejava ma sarebbe il mio.
MatrixFrog,

1
Questo è anche quello che tendo a fare. Mantiene il codice pulito secondo me.
Koray Tugay,

34

Non usare mai null. Non permetterlo.

Nelle mie classi, la maggior parte dei campi e delle variabili locali hanno valori predefiniti non nulli e aggiungo dichiarazioni di contratto (asserzioni sempre attive) ovunque nel codice per assicurarmi che questo venga applicato (poiché è più conciso e più espressivo che lasciarlo venire come un NPE e quindi dover risolvere il numero di riga, ecc.).

Una volta adottata questa pratica, ho notato che i problemi sembravano risolversi da soli. Prenderesti le cose molto prima nel processo di sviluppo solo per caso e ti rendevi conto di avere un punto debole ... e, cosa più importante, aiuta a incapsulare le preoccupazioni dei diversi moduli, i diversi moduli possono "fidarsi" l'uno dell'altro, e non più sporcare il codice con if = null elsecostrutti!

Questa è una programmazione difensiva e si traduce in un codice molto più pulito a lungo termine. Disinfettare sempre i dati, ad esempio qui applicando standard rigidi e i problemi scompaiono.

class C {
    private final MyType mustBeSet;
    public C(MyType mything) {
       mustBeSet=Contract.notNull(mything);
    }
   private String name = "<unknown>";
   public void setName(String s) {
      name = Contract.notNull(s);
   }
}


class Contract {
    public static <T> T notNull(T t) { if (t == null) { throw new ContractException("argument must be non-null"); return t; }
}

I contratti sono come test di mini-unità che sono sempre in esecuzione, anche in produzione, e quando le cose falliscono, sai perché, piuttosto che un NPE casuale devi in ​​qualche modo capire.


perché questo dovrebbe essere sottoposto a downgrade? nella mia esperienza, questo è di gran lunga superiore agli altri approcci, mi piacerebbe sapere perché no
ianpojman,

Sono d'accordo, questo approccio previene i problemi relativi ai valori null, piuttosto che risolverli mediante il controllo puntuale dei codici null ovunque.
ChrisBlom,

7
Il problema con questo approccio è che se il nome non viene mai impostato, ha il valore "<sconosciuto>", che si comporta come un valore impostato. Ora diciamo che devo verificare se il nome non è mai stato impostato (sconosciuto), devo fare un confronto tra stringhe con il valore speciale "<sconosciuto>".
Steve Kuo,

1
Vero buon punto Steve. Quello che faccio spesso è avere quel valore come costante, ad es. String finale statica pubblica UNSET = "__ unset" ... private String field = UNSET ... then private boolean isSet () {return UNSET.equals (field); }
ianpojman,

IMHO, questa è un'implementazione di Null Object Pattern con un'implementazione autonoma di Facoltativo (contratto). Come si comporta nella classe di persistenza? Non vedo applicabile in quel caso.
Kurapika,

31

Guava, una libreria di base molto utile di Google, ha un'API bella e utile per evitare i null. Trovo che UsingAndAvoidingNullExplained sia stato molto utile.

Come spiegato nel wiki:

Optional<T>è un modo per sostituire un riferimento T nullable con un valore non nullo. Un Opzionale può contenere un riferimento T non nullo (nel qual caso diciamo che il riferimento è "presente"), oppure può non contenere nulla (nel qual caso diciamo che il riferimento è "assente"). Non si dice mai che "contenga null".

Uso:

Optional<Integer> possible = Optional.of(5);
possible.isPresent(); // returns true
possible.get(); // returns 5

@CodyGuldner Giusto, Cody. Ho fornito una citazione pertinente dal link per dare più contesto.
Murat Derya Özen,

25

Questo è un problema molto comune per ogni sviluppatore Java. Quindi esiste un supporto ufficiale in Java 8 per affrontare questi problemi senza codice disordinato.

Java 8 ha introdotto java.util.Optional<T>. È un contenitore che può contenere o meno un valore non nullo. Java 8 ha fornito un modo più sicuro per gestire un oggetto il cui valore può essere nullo in alcuni casi. Si ispira alle idee di Haskell e Scala .

In breve, la classe Optional include metodi per trattare esplicitamente i casi in cui un valore è presente o assente. Tuttavia, il vantaggio rispetto ai riferimenti null è che la classe <T> opzionale ti costringe a pensare al caso in cui il valore non è presente. Di conseguenza, è possibile impedire eccezioni indesiderate del puntatore null.

Nell'esempio sopra abbiamo una fabbrica di servizi domestici che restituisce una maniglia a più apparecchi disponibili in casa. Ma questi servizi potrebbero essere o non essere disponibili / funzionali; significa che potrebbe provocare una NullPointerException. Invece di aggiungere una ifcondizione nulla prima di utilizzare qualsiasi servizio, includiamola in <Servizio> opzionale.

AVVOLGIMENTO ALL'OPZIONE <T>

Consideriamo un metodo per ottenere un riferimento di un servizio da una fabbrica. Invece di restituire il riferimento del servizio, avvolgerlo con Opzionale. Fa sapere all'utente API che il servizio restituito può o meno essere disponibile / funzionale, utilizzare in modo difensivo

public Optional<Service> getRefrigertorControl() {
      Service s = new  RefrigeratorService();
       //...
      return Optional.ofNullable(s);
   }

Come vedi Optional.ofNullable()fornisce un modo semplice per ottenere il riferimento spostato. Esistono altri modi per ottenere il riferimento di Facoltativo, e Optional.empty()& Optional.of(). Uno per restituire un oggetto vuoto invece di risintonizzare null e l'altro per avvolgere un oggetto non nullable, rispettivamente.

COSÌ COME ESATTAMENTE AIUTA A EVITARE UN CONTROLLO NULL?

Dopo aver avvolto un oggetto di riferimento, Opzionale fornisce molti metodi utili per invocare metodi su un riferimento a capo senza NPE.

Optional ref = homeServices.getRefrigertorControl();
ref.ifPresent(HomeServices::switchItOn);

Facoltativo.ifPresent richiama il consumatore specificato con un riferimento se si tratta di un valore non nullo. Altrimenti, non fa nulla.

@FunctionalInterface
public interface Consumer<T>

Rappresenta un'operazione che accetta un singolo argomento di input e non restituisce alcun risultato. A differenza della maggior parte delle altre interfacce funzionali, si prevede che il consumatore operi tramite effetti collaterali. È così pulito e facile da capire. Nell'esempio di codice sopra, HomeService.switchOn(Service)viene invocato se il riferimento di mantenimento opzionale è non nullo.

Usiamo l'operatore ternario molto spesso per verificare la condizione nulla e restituiamo un valore alternativo o un valore predefinito. Opzionale fornisce un altro modo per gestire la stessa condizione senza controllare null. Optional.orElse (defaultObj) restituisce defaultObj se l'Opzionale ha un valore nullo. Usiamo questo nel nostro codice di esempio:

public static Optional<HomeServices> get() {
    service = Optional.of(service.orElse(new HomeServices()));
    return service;
}

Ora HomeServices.get () fa la stessa cosa, ma in un modo migliore. Verifica se il servizio è già stato inizializzato o meno. In tal caso, restituire lo stesso o creare un nuovo nuovo servizio. L'opzione <T> .orElse (T) consente di restituire un valore predefinito.

Infine, ecco il nostro NPE e il codice null check-free:

import java.util.Optional;
public class HomeServices {
    private static final int NOW = 0;
    private static Optional<HomeServices> service;

public static Optional<HomeServices> get() {
    service = Optional.of(service.orElse(new HomeServices()));
    return service;
}

public Optional<Service> getRefrigertorControl() {
    Service s = new  RefrigeratorService();
    //...
    return Optional.ofNullable(s);
}

public static void main(String[] args) {
    /* Get Home Services handle */
    Optional<HomeServices> homeServices = HomeServices.get();
    if(homeServices != null) {
        Optional<Service> refrigertorControl = homeServices.get().getRefrigertorControl();
        refrigertorControl.ifPresent(HomeServices::switchItOn);
    }
}

public static void switchItOn(Service s){
         //...
    }
}

Il post completo è NPE e Null codice senza check ... Davvero? .


C'è un controllo nullo nel codice sopra - if(homeServices != null) {che può essere modificato in homeServices.ifPresent(h -> //action);
KrishPrabakar il

23

Mi piacciono gli articoli di Nat Pryce. Ecco i link:

Negli articoli c'è anche un collegamento a un repository Git per un Java Maybe Type che trovo interessante, ma non credo che da solo potrebbe ridurre il gonfiore del codice di controllo. Dopo aver fatto qualche ricerca su Internet, penso ! = Il bloat del codice null potrebbe essere ridotto principalmente da un'attenta progettazione.


Michael Feathers ha scritto un breve e interessante testo su approcci come quello che hai menzionato: manuelp.newsblur.com/site/424
ivan.aguirre,

21

Ho provato il NullObjectPatternma per me non è sempre il modo migliore di andare. A volte ci sono quando una "nessuna azione" non è appropriata.

NullPointerExceptionè un'eccezione di runtime che significa che è colpa degli sviluppatori e con sufficiente esperienza ti dice esattamente dove si trova l'errore.

Ora alla risposta:

Cerca di rendere tutti i tuoi attributi e i suoi accessori il più privati ​​possibile o evita di esporli affatto ai clienti. Naturalmente puoi avere i valori degli argomenti nel costruttore, ma riducendo l'ambito non permetti alla classe client di passare un valore non valido. Se è necessario modificare i valori, è sempre possibile crearne uno nuovo object. Controlli i valori nel costruttore una sola volta e nel resto dei metodi puoi essere quasi sicuro che i valori non siano nulli.

Naturalmente, l'esperienza è il modo migliore per comprendere e applicare questo suggerimento.

Byte!


19

Probabilmente la migliore alternativa per Java 8 o più recente è usare la Optionalclasse.

Optional stringToUse = Optional.of("optional is there");
stringToUse.ifPresent(System.out::println);

Ciò è particolarmente utile per lunghe catene di possibili valori null. Esempio:

Optional<Integer> i = Optional.ofNullable(wsObject.getFoo())
    .map(f -> f.getBar())
    .map(b -> b.getBaz())
    .map(b -> b.getInt());

Esempio su come generare un'eccezione su null:

Optional optionalCarNull = Optional.ofNullable(someNull);
optionalCarNull.orElseThrow(IllegalStateException::new);

Java 7 ha introdotto il Objects.requireNonNullmetodo che può essere utile quando qualcosa deve essere verificato per nullità. Esempio:

String lowerVal = Objects.requireNonNull(someVar, "input cannot be null or empty").toLowerCase();

17

Posso rispondere più in generale!

Di solito affrontiamo questo problema quando i metodi ottengono i parametri nel modo in cui non ci aspettavamo (una chiamata errata al metodo è colpa del programmatore). Ad esempio: ti aspetti di ottenere un oggetto, invece ottieni un null. Ti aspetti di ottenere una stringa con almeno un carattere, invece ottieni una stringa vuota ...

Quindi non c'è differenza tra:

if(object == null){
   //you called my method badly!

}

o

if(str.length() == 0){
   //you called my method badly again!
}

Entrambi vogliono assicurarsi di aver ricevuto parametri validi, prima di eseguire qualsiasi altra funzione.

Come menzionato in alcune altre risposte, per evitare i problemi di cui sopra è possibile seguire il modello Design by contract . Si prega di consultare http://en.wikipedia.org/wiki/Design_by_contract .

Per implementare questo modello in Java, è possibile utilizzare le annotazioni core Java come javax.annotation.NotNull o utilizzare librerie più sofisticate come Hibernate Validator .

Solo un campione:

getCustomerAccounts(@NotEmpty String customerId,@Size(min = 1) String accountType)

Ora puoi sviluppare in sicurezza la funzione principale del tuo metodo senza dover controllare i parametri di input, proteggendo i tuoi metodi da parametri imprevisti.

Puoi fare un passo ulteriore e assicurarti che nella tua applicazione possano essere creati solo pojos validi. (campione dal sito di validazione ibernazione)

public class Car {

   @NotNull
   private String manufacturer;

   @NotNull
   @Size(min = 2, max = 14)
   private String licensePlate;

   @Min(2)
   private int seatCount;

   // ...
}

javaxè, per definizione, non "core Java".
Tomas,

17

Ignoro fortemente le risposte che suggeriscono di usare gli oggetti null in ogni situazione. Questo modello può rompere il contratto e seppellire sempre più a fondo i problemi invece di risolverli, senza menzionare che l'uso improprio creerà un altro mucchio di codice della caldaia che richiederà una manutenzione futura.

In realtà, se qualcosa restituito da un metodo può essere nullo e il codice chiamante deve prendere una decisione al riguardo, dovrebbe esserci una chiamata precedente che assicura lo stato.

Inoltre, tieni presente che quel modello di oggetti null avrà fame di memoria se utilizzato senza cura. Per questo - l'istanza di un NullObject deve essere condivisa tra i proprietari e non essere un'istanza unigue per ognuno di questi.

Inoltre, non consiglierei di utilizzare questo modello in cui il tipo deve essere una rappresentazione di tipo primitivo - come entità matematiche, che non sono scalari: vettori, matrici, numeri complessi e oggetti POD (Plain Old Data), che devono contenere lo stato sotto forma di tipi predefiniti Java. In quest'ultimo caso si finirà per chiamare metodi getter con risultati arbitrari. Ad esempio, cosa dovrebbe restituire un metodo NullPerson.getName ()?

Vale la pena considerare questi casi per evitare risultati assurdi.


La soluzione con "hasBackground ()" ha uno svantaggio: non è thread-safe. Se è necessario chiamare due metodi anziché uno, è necessario sincronizzare l'intera sequenza in un ambiente multi-thread.
pkalinow,

@pkalinow Hai fatto un esempio inventato solo per sottolineare che questa soluzione ha uno svantaggio. Se il codice non è pensato per essere eseguito in un'applicazione multithread, non ci sono svantaggi. Potrei darti probabilmente il 90% del tuo codice che non è thread-safe. Non stiamo parlando qui di questo aspetto del codice, stiamo parlando del modello di progettazione. E il multithreading è un argomento a sé stante.
luke1985,

Ovviamente in un'applicazione a thread singolo non è un problema. Ho dato quel commento perché a volte è un problema.
pkalinow,

@pkalinow Se studi questo argomento più da vicino, scoprirai che il modello di progettazione Null Object non risolverà i problemi di multithreading. Quindi è irrilevante. E ad essere sincero, ho trovato posti in cui questo schema si adatterebbe bene, quindi la mia risposta originale è un po 'sbagliata, in realtà.
luke1985,

16
  1. Non inizializzare mai le variabili su null.
  2. Se (1) non è possibile, inizializzare tutte le raccolte e gli array per svuotare le raccolte / gli array.

Fallo nel tuo codice e puoi evitare! = Controlli null.

Il più delle volte i controlli null sembrano proteggere i loop da raccolte o array, quindi inizializzali vuoti, non avrai bisogno di controlli null.

// Bad
ArrayList<String> lemmings;
String[] names;

void checkLemmings() {
    if (lemmings != null) for(lemming: lemmings) {
        // do something
    }
}



// Good
ArrayList<String> lemmings = new ArrayList<String>();
String[] names = {};

void checkLemmings() {
    for(lemming: lemmings) {
        // do something
    }
}

C'è un piccolo sovraccarico in questo, ma ne vale la pena per un codice più pulito e meno NullPointerExceptions.



3
+1 Questo sono d'accordo. Non si devono mai restituire oggetti metà inizializzati. Il codice relativo a Jaxb e il codice bean non sono specifici per questo. È una cattiva pratica. Tutte le raccolte dovrebbero essere inizializzate e tutti gli oggetti dovrebbero esistere senza (idealmente) nessun riferimento null. Considera un oggetto che contiene una raccolta. Controllare che l'oggetto non sia nullo, che la raccolta non sia nulla e che la raccolta non contenga oggetti nulli è irragionevole e insensato.
ggb667,

15

Questo è l'errore più comune che si è verificato per la maggior parte degli sviluppatori.

Abbiamo diversi modi per gestirlo.

Approccio 1:

org.apache.commons.lang.Validate //using apache framework

notNull (oggetto Object, messaggio String)

Approccio 2:

if(someObject!=null){ // simply checking against null
}

Approccio 3:

@isNull @Nullable  // using annotation based validation

Approccio 4:

// by writing static method and calling it across whereever we needed to check the validation

static <T> T isNull(someObject e){  
   if(e == null){
      throw new NullPointerException();
   }
   return e;
}

Anno Domini. 4. Non è molto utile: quando si controlla se un puntatore è null, probabilmente si desidera chiamare un metodo su di esso. Chiamare un metodo su null ti dà lo stesso comportamento - NullPointerException.
pkalinow,

11
public static <T> T ifNull(T toCheck, T ifNull) {
    if (toCheck == null) {
           return ifNull;
    }
    return toCheck;
}

2
Cosa c'è che non va in questo metodo, penso che @tltester voglia solo dare un valore predefinito se è nullo, il che ha senso.
Sawyer,

1
C'è un tale metodo in Apache commons-lang: ObjectUtils.defaultIfNull(). C'è un altro generale: ObjectUtils.firstNonNull()che può essere utilizzato per attuare una strategia degradante:firstNonNull(bestChoice, secondBest, thirdBest, fallBack);
ivant
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.