Recupero di un valore senza dover annullare il controllo in Java


15

Molte volte mi ritrovo a controllare nulla durante il recupero di un valore da una gerarchia di dati per evitare NullPointerExceptions, che trovo essere soggetto a errori e necessita di molta piastra di caldaia.

Ho scritto una routine molto semplice che mi consente di saltare il controllo null durante il recupero di un oggetto ...

public final class NoNPE {

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

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

Lo uso un po 'come questo ...

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

Quanto sopra mi dà come risultato ottenere un oggetto Room o null, senza dover controllare tutti i livelli padre.

Cosa ne pensi di quanto sopra? Sto creando un modello problematico? C'è un modo migliore per farlo secondo te?


1
Dato che apparentemente stai utilizzando Java 8, posso suggerire di prendere in considerazione la riprogettazione dell'applicazione da utilizzare java.util.Optionalanziché i valori null per rappresentare i dati mancanti? Ciò fornisce utili utilità sia per il caso che descrivi sia per i casi in cui vorresti continuare con i dati predefiniti anziché restituire una condizione di errore alla fine della catena.
Periata Breatta,

Penso che tu abbia sostanzialmente riscoperto la Option(o Maybe) monade :)
Andres F.

Potrebbe essere possibile restituire Opzionale invece di T o null: in questo modo è possibile utilizzare direttamente il metodo orElse (). 18 mesi dopo, ma potrebbe aiutare qualcuno.
Benj,

Un altro approccio è menzionato in questo post illegaleargumentexception.blogspot.com/2015/03/… , uno di questi sta usando una libreria chiamata kludje che ha una sintassi molto interessante
Benj,

Risposte:


13

La tua soluzione è molto intelligente. Il problema che vedo è il fatto che non sai perché hai un null? Era perché la casa non aveva stanze? Era perché la città non aveva case? Era perché il paese non aveva città? È stato perché c'era un nullnella posizione 0 della collezione a causa di un errore anche quando ci sono case nelle posizioni 1 e successive?

Se usi estensibe la NonPEclasse, avrai seri problemi di debug. Penso che sia meglio sapere dove si rompe esattamente la catena piuttosto che ottenere silenziosamente un nullche potrebbe nascondere un errore più profondo.

Anche questo viola la Legge di Demetra : country.getTown().getHouses().get(0).getLivingRoom(). Più spesso, la violazione di alcuni buoni principi ti rende necessario implementare soluzioni non ortodosse per risolvere il problema causato dalla violazione di tale principio.

La mia raccomandazione è di usarlo con cautela e provare a risolvere il difetto di progettazione che ti costringe a incorrere nell'antipasto del disastro ferroviario (quindi non devi usarlo NonPEovunque). Altrimenti potresti avere dei bug che saranno difficili da rilevare.


Bella risposta. Sì, non saprò dove ho ottenuto il null nella catena. In molti casi, anche se non mi interessa e il fatto di non dover controllare nulla significa che il codice è più leggibile e meno soggetto all'errore del bollettino. Ma sì, hai ragione in alcuni casi in cui ho bisogno di prendere una decisione logica diversa se un oggetto genitore è nullo, questo causerebbe un problema. Il metodo convenzionale o la classe opzionale potrebbe essere una soluzione più sicura lì.
Eurig Jones,

In generale, quando usi la Optionmonade, non ti importa dove si trova il valore assente nella catena. Quando ti interessa, probabilmente useresti un tipo diverso, come Either.
Andres F.

L'approccio del PO è simile a quello di C # 6 ?.e degli ?[]operatori. Un esempio di quando potresti voler usare una cosa simile sono le impostazioni gerarchiche sul lato server. var shouldDoThing = settings?.a?.b?.c ?? defaultSetting;Chi se ne frega perché una parte di ciò era nulla? Forse non sei riuscito a recuperare le impostazioni. Forse hai deciso di rimuovere una sezione delle impostazioni. In ogni caso, non puoi mai davvero contare su come ottenere le impostazioni del server, quindi un valore predefinito è di solito una buona idea e è improbabile che ti interessi perché non puoi ottenere l'impostazione effettiva a meno che non accada molto spesso quando non dovrebbe .
chris,

Ora non sto dicendo che è strettamente meglio o peggio che localizzare i valori predefiniti e semplicemente recuperare il valore desiderato attraverso gli accessi normali settings.a.b.c. Quindi di nuovo, questo è un singolo esempio isolato.
chris,

10

L'idea va bene, davvero molto bene. Dal momento che Optionalesistono i tipi Java 8 , è possibile trovare una spiegazione dettagliata nel tipo Opzionale Java . Un esempio di ciò che hai pubblicato è

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

E più avanti.


1
Sì, ero a conoscenza della classe Optional, sia da Java 8 che da Guava e sono davvero utili. Ma non puoi semplicemente recuperare un oggetto perché normalmente rendi il codice un po 'più difficile da leggere e anche un po' meno performante. Ma il lato positivo è che ci sono molti operatori molto utili forniti dalla classe Optional.
Eurig Jones,

3
@EurigJones Non credo che il codice diventi meno performante. La leggibilità agli occhi di chi guarda, ma direi che Optionalè la soluzione più leggibile dei due, se non altro perché - a differenza della tua proposta - è un linguaggio molto comune . È ancora più conciso del tuo!
Andres F.

0

Il tuo metodo funziona abbastanza bene per lo scopo previsto, sebbene restituisca nulls quando ottieni un NullPointerExceptionsuono come un cattivo design.

Cerca di evitare nulls quando puoi e passali solo quando rappresentano qualcosa o hanno un significato speciale e restituiscili solo quando rappresentano / significano qualcosa - altrimenti dovresti lanciare a NullPointerException. Questo evita bug e confusione. Se Objectnon dovrebbe essere null, NullPointerdovrebbe essere lanciato. Se un oggetto può essere, nullallora nulla andrà storto quando viene passato. Altrimenti il ​​metodo sopra funziona.


0

Sento il tuo dolore, ma la soluzione proposta è una cattiva idea.

  • Se uno dei getter lancia un NPE per qualche altro motivo, lo ignorerai.
  • C'è il rischio che quella lambda interiore si trasformi in un codice orribile. Ad esempio, se c'è un nuovo requisito per restituire una costante speciale quando non ci sono case in città, un programmatore pigro può estendere il lamda, lasciando tutto avvolto NoNPE.get.
  • Come già accennato, Optional.mapè quello che stai cercando.
  • La penalità per la creazione di una nuova istanza di NullPointerException è spesso significativa. Sono molti microsecondi, soprattutto perché lo stack delle chiamate sta diventando più grande. È difficile prevedere dove verrà utilizzata la tua utility.

Come nota a margine, NoNPEInterfaceè un duplicato di java.util.function.Supplier.

In alcuni casi potresti prendere in considerazione l'utilizzo di utils di valutazione di espressioni presenti in molti framework (ad esempio: EL, SpEL):

evaluateProperty(country, "town.houses[0].livingRoom")

Ok per i modelli di pagine Web, ma generalmente lento da sviluppare (nessun controllo del tempo di compilazione) e lento da eseguire.
Kevin Cline,
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.