2012-04-30 16 views
25

Immagina di avere una famiglia di classe. Contiene un elenco di persone. Ogni persona (di classe) contiene un indirizzo (di classe). Ogni indirizzo (di classe) contiene un codice postale (di classe). Qualsiasi classe "intermedia" può essere nulla.Java: evitare il controllo di null nelle classi nidificate (Deep Null checking)

Quindi, c'è un modo semplice per arrivare a PostalCode senza dover controllare null in ogni passaggio? vale a dire, esiste un modo per evitare il seguente codice di concatenamento a margherita? So che non esiste una soluzione Java "nativa", ma speravo che qualcuno fosse a conoscenza di una biblioteca o qualcosa del genere. (Controllato Commons & Guava e non ho visto nulla)

if(family != null) { 
    if(family.getPeople() != null) { 
     if(family.people.get(0) != null) { 
      if(people.get(0).getAddress() != null) { 
       if(people.get(0).getAddress().getPostalCode() != null) { 
        //FINALLY MADE IT TO DO SOMETHING!!! 
       } 
      } 
     } 
    } 
} 

No, non si può cambiare la struttura. È da un servizio di cui non ho il controllo.

No, non posso usare Groovy ed è pratico l'operatore "Elvis".

No, preferisco non aspettare per Java 8: D

Non posso credere che io sono il primo dev mai ad ammalarsi 'n stanco di scrivere codice come questo, ma mi rifugio' Sono stato in grado di trovare una soluzione.

Idee?

Grazie

-
llappall

+0

Siamo spiacenti, sei bloccato. Alcune persone usano l'operatore condizionale trinario per renderlo un po 'meno denso, ma è sempre lo stesso bytecode, solo più difficile da leggere. –

+4

"* Non posso credere di essere il primo dev di sempre ad ammalarsi, stanco di scrivere un codice come questo *" Beh, non lo sei. – user1329572

+0

Sicuro. Ma non credo che tu possa più beaty il codice! Scusate! –

risposta

1

Non come una grande idea, ma come circa la cattura eccezione:

try 
    { 
     PostalCode pc = people.get(0).getAddress().getPostalCode(); 
    } 
    catch(NullPointerException ex) 
    { 
     System.out.println("Gotcha"); 
    } 
6

Il più vicino si può ottenere è di approfittare della breve -cut regole in condizionali:

if(family != null && family.getPeople() != null && family.people.get(0) != null && family.people.get(0).getAddress() != null && family.people.get(0).getAddress().getPostalCode() != null) { 
        //FINALLY MADE IT TO DO SOMETHING!!! 

} 

A proposito, catturare un'eccezione invece di testare la condizione in anticipo è un'idea orribile.

+0

hai qualche accesso '}' lì (ho dimenticato di rimuoverli quando refactored) – amit

+0

Grazie, @amit. Risolto ora. –

11

Il codice si comporta come

if(family != null && 
    family.getPeople() != null && 
    family.people.get(0) != null && 
    family.people.get(0).getAddress() != null && 
    family.people.get(0).getAddress().getPostalCode() != null) { 
     //My Code 
} 

Grazie a short circuiting evaluation, anche questo è sicuro, dal momento che la seconda condizione non sarà valutato se il primo è falso, il 3 °, non sarà valutato se il 2 ° è falso, .... e non otterrai NPE perché se lo fosse.

+0

'null' vuole sempre rimanere nei nostri codici! –

3

Se è raro, è possibile ignorare i controlli null e fare affidamento su NullPointerException. "Raro" a causa di possibili problemi di prestazioni (dipende, di solito, compila una traccia di stack che può essere costosa).

Diverso da quello che 1) un metodo di supporto specifico che verifica la presenza di null per ripulire quel codice o 2) Fai approccio generico utilizzando la riflessione e una stringa del tipo:

checkNonNull(family, "people[0].address.postalcode") 

Attuazione lasciato come esercizio.

+0

Anche la riflessione non è esattamente economica. –

+0

Sì, in effetti può essere lento anche @paul. Soprattutto la ricerca dei metodi ecc. L'effettiva invocazione del metodo può essere abbastanza veloce, ma dipende ancora (altre ottimizzazioni potrebbero essere più difficili per la VM). Pertanto l'archiviazione in cache delle ricerche metodo/campo è di solito importante dalla mia esperienza, se necessario. Ma soprattutto dipende da quanto spesso il codice viene utilizzato. –

1

Invece di utilizzare null, è possibile utilizzare una versione del modello di progettazione "oggetto nullo".Per esempio:

public class Family { 
    private final PersonList people; 
    public Family(PersonList people) { 
     this.people = people; 
    } 

    public PersonList getPeople() { 
     if (people == null) { 
      return PersonList.NULL; 
     } 
     return people; 
    } 

    public boolean isNull() { 
     return false; 
    } 

    public static Family NULL = new Family(PersonList.NULL) { 
     @Override 
     public boolean isNull() { 
      return true; 
     } 
    }; 
} 


import java.util.ArrayList; 

public class PersonList extends ArrayList<Person> { 
    @Override 
    public Person get(int index) { 
     Person person = null; 
     try { 
      person = super.get(index); 
     } catch (ArrayIndexOutOfBoundsException e) { 
      return Person.NULL; 
     } 
     if (person == null) { 
      return Person.NULL; 
     } else { 
      return person; 
     } 
    } 
    //... more List methods go here ... 

    public boolean isNull() { 
     return false; 
    } 

    public static PersonList NULL = new PersonList() { 
     @Override 
     public boolean isNull() { 
      return true; 
     } 
    }; 
} 

public class Person { 
    private Address address; 

    public Person(Address address) { 
     this.address = address; 
    } 

    public Address getAddress() { 
     if (address == null) { 
      return Address.NULL; 
     } 
     return address; 
    } 
    public boolean isNull() { 
     return false; 
    } 

    public static Person NULL = new Person(Address.NULL) { 
     @Override 
     public boolean isNull() { 
      return true; 
     } 
    }; 
} 

etc etc etc 

Allora la vostra istruzione if può diventare:

if (!family.getPeople().get(0).getAddress().getPostalCode.isNull()) {...} 

E 'non ottimale dal:

  • sei bloccato facendo oggetti NULL per ogni classe,
  • E' difficile per rendere generici questi oggetti, quindi sei bloccato a creare una versione null-object di ogni List, Map, ecc. che vuoi usare, e
  • Ci sono potenzialmente alcuni problemi divertenti con la sottoclasse e quale NULL usare.

Ma se davvero odi i tuoi == null s, questa è una via d'uscita.

0

Stavo solo cercando la stessa cosa (il mio contesto: un gruppo di classi JAXB create automaticamente, e in qualche modo ho queste lunghe catene di margherite di .getFoo().getBar().... Invariabilmente, una volta ogni tanto una delle chiamate nel mezzo restituisce null, che causa NPE.

Qualcosa che ho iniziato a trafficare con un po di tempo fa è basato sulla riflessione. Sono sicuro che possiamo renderlo più bello e più efficiente (mettere in cache il riflesso, per prima cosa e definire anche i metodi "magici" come ._all per iterare automaticamente su tutti gli elementi di una raccolta, se qualche metodo nel mezzo restituisce una raccolta). Non bello, ma forse qualcuno potrebbe dirci se c'è già qualcosa di meglio là fuori:

/** 
* Using {@link java.lang.reflect.Method}, apply the given methods (in daisy-chain fashion) 
* to the array of Objects x. 
* 
* <p>For example, imagine that you'd like to express: 
* 
* <pre><code> 
* Fubar[] out = new Fubar[x.length]; 
* for (int i=0; {@code i<x.length}; i++) { 
* out[i] = x[i].getFoo().getBar().getFubar(); 
* } 
* </code></pre> 
* 
* Unfortunately, the correct code that checks for nulls at every level of the 
* daisy-chain becomes a bit convoluted. 
* 
* <p>So instead, this method does it all (checks included) in one call: 
* <pre><code> 
* Fubar[] out = apply(new Fubar[0], x, "getFoo", "getBar", "getFubar"); 
* </code></pre> 
* 
* <p>The cost, of course, is that it uses Reflection, which is slower than 
* direct calls to the methods. 
* @param type the type of the expected result 
* @param x the array of Objects 
* @param methods the methods to apply 
* @return 
*/ 
@SuppressWarnings("unchecked") 
public static <T> T[] apply(T[] type, Object[] x, String...methods) { 
    int n = x.length; 
    try { 
     for (String methodName : methods) { 
      Object[] out = new Object[n]; 
      for (int i=0; i<n; i++) { 
       Object o = x[i]; 
       if (o != null) { 
        Method method = o.getClass().getMethod(methodName); 
        Object sub = method.invoke(o); 
        out[i] = sub; 
       } 
      } 
      x = out; 
     } 
    T[] result = (T[])Array.newInstance(type.getClass().getComponentType(), n); 
    for (int i=0; i<n; i++) { 
      result[i] = (T)x[i]; 
    } 
      return result; 
    } catch (NoSuchMethodException | IllegalAccessException | IllegalArgumentException | InvocationTargetException e) { 
      throw new RuntimeException(e); 
    } 
} 
0

Se, nel caso, si utilizza java8, è possibile utilizzare;

resolve(() -> people.get(0).getAddress().getPostalCode()); 
    .ifPresent(System.out::println); 

: 
public static <T> Optional<T> resolve(Supplier<T> resolver) { 
    try { 
     T result = resolver.get(); 
     return Optional.ofNullable(result); 
    } 
    catch (NullPointerException e) { 
     return Optional.empty(); 
    } 
} 

REF: avoid null checks

+0

Questa soluzione si basa sulla cattura del NPE che si comporta molto male. – mojoken

0

Anche se questo post è quasi cinque anni, potrei avere un'altra soluzione per l'età vecchia questione di come gestire NullPointerException s.

In poche parole:

end: { 
    List<People> people = family.getPeople();   if(people == null || people.isEmpty()) break end; 
    People person = people.get(0);      if(person == null) break end; 
    Address address = person.getAddress();    if(address == null) break end; 
    PostalCode postalCode = address.getPostalCode();  if(postalCode == null) break end; 

    System.out.println("Do stuff"); 
} 

Dal momento che c'è un sacco di codice legacy ancora in uso, utilizzando Java 8 e Optional non è sempre un'opzione.

Ogni volta che ci sono profondamente classi nidificate coinvolti (JAXB, SOAP, JSON, è il nome ...) e Law of Demeter non viene applicata, che, fondamentalmente, controlla tutto e vedere se ci sono possibili NPE agguato intorno.

La mia soluzione proposta mira alla leggibilità e non dovrebbe essere utilizzata se non sono coinvolte almeno 3 o più classi nidificate (quando dico nidificato, non intendo lo Nested classes nel contesto formale). Poiché il codice viene letto più di quanto non sia scritto, una rapida occhiata alla parte sinistra del codice renderà il suo significato più chiaro rispetto all'utilizzo di istruzioni if-else profondamente annidate.

Se avete bisogno della parte il resto, è possibile utilizzare questo modello:

boolean prematureEnd = true; 

end: { 
    List<People> people = family.getPeople();   if(people == null || people.isEmpty()) break end; 
    People person = people.get(0);      if(person == null) break end; 
    Address address = person.getAddress();    if(address == null) break end; 
    PostalCode postalCode = address.getPostalCode();  if(postalCode == null) break end; 

    System.out.println("Do stuff"); 
    prematureEnd = false; 
} 

if(prematureEnd) { 
    System.out.println("The else part"); 
} 

Alcuni IDE si romperà questa formattazione, a meno che non si indica loro di non (vedi this question).

I tuoi condizionali devono essere invertiti: devi dire al codice quando deve interrompersi, non quando deve continuare.

Un'altra cosa: il codice è ancora soggetto a rotture. Devi usare if(family.getPeople() != null && !family.getPeople().isEmpty()) come prima riga del tuo codice, altrimenti un elenco vuoto genererà un NPE.