Stanco di pointer NULL Eccezioni? Valutare la possibilità di utilizzare Java opzionale SE8.

Un uomo molto saggio ha detto che un tempo stesso un vero programmatore Java non è stato affrontato da un’eccezione del puntatore nullo. A parte le battute, il riferimento nullo dà origine a molti problemi perché è spesso usato per indicare l’assenza di un valore. Java SE8 presenta una nuova classe chiamata Java.util.optional che può alleviare alcuni di questi problemi.

Iniziamo con un esempio per vedere i pericoli dell’uso di NULL. Pensa, ad esempio, in una struttura di oggetti nidificati per rappresentare il computer, come illustrato nella figura 1.

Figura 1: Struttura annidata per rappresentare il computer

Quali problemi possono presentare il seguente codice?

String version = computer.getSoundcard().getUSB().getVersion();

Il codice sembra davvero logico. Tuttavia, molti computer (ad esempio, il Raspberry PI) sono distribuiti senza una scheda audio. Quindi, qual è il risultato di GempoundCard ()?

Una pratica comune è restituire il riferimento NULL per indicare l’assenza di scheda audio. Sfortunatamente, ciò significa che la chiamata a Getusb () cercherà di restituire la porta USB di un riferimento NULL; Di conseguenza, un’eccezione NullPointerException sarà gettata in fase di runtime e il programma smetterà di funzionare. Immagina che il tuo programma sia stato in esecuzione sulla squadra di un cliente: cosa direbbe quel cliente se il programma, improvvisamente, fallito?

per fornire un piccolo contesto storico, Tony Hoaare -UR dei Giants dal computer Ha scritto: “Io chiamo il mio errore dei miliardi di dollari: l’invenzione del riferimento nullo nel 1965. Non potevo resistere alla tentazione di inserire un riferimento nullo. Era così facile implementarlo …”.

Cosa può essere fatto per evitare eccezioni del puntatore NULL non intenzionale? Un atteggiamento difensivo può essere adottato e aggiunto controlli per evitare riferimenti nullo, come mostrato nell’elenco 1:

String version = "UNKNOWN";if(computer != null){ Soundcard soundcard = computer.getSoundcard(); if(soundcard != null){ USB usb = soundcard.getUSB(); if(usb != null){ version = usb.getVersion(); } }}

elenco 1

Tuttavia, è facile vederlo subito l’elenco 1 inizia a perdere eleganza a causa dei controlli nidificati. Sfortunatamente, abbiamo bisogno di un sacco di codice ripetitivo per assicurarti di non ottenere un errore NullPointerException. Inoltre, è fastidioso che questi controlli interferiscano con la logica del business. Infatti, riducono la leggibilità generale del programma.

Inoltre, è un processo incline agli errori: cosa succederebbe se hai dimenticato di controllare che una proprietà possa essere nullo? In questo articolo, sosterrò che l’uso di NULL per rappresentare l’assenza di un valore costituisce un approccio errato. Ciò di cui abbiamo bisogno, è un modo migliore per modellare l’assenza e la presenza di un valore.

Per fornire un po ‘di contesto all’analisi, esaminare quali altri linguaggi di programmazione offrono.

Quale Sono le alternative all’uso di NULL?

Lingue come Groovy hanno un operatore di navigazione sicuro rappresentato da “?” Per superare i riferimenti potenzialmente nulle senza rischi. (Nota che C # avrà anche questo operatore, e che era stato proposto di includerlo in Java 7, anche se non è stato possibile farlo in quella versione.) Funziona come segue:

String version = computer?.getSoundcard()?.getUSB()?.getVersion();

In questo caso, una variabile null verrà assegnata un valore nullo se il computer è null o getoundcard () restituisce null o gettusb () returve null. Non è necessario introdurre condizioni nidificate complesse per verificare la presenza di null. Inoltre, Groovy ha anche l’operatore Elvis “?:” (Se lo guardi, riconoscerai la famosa acconciatura Elvis), che può essere utilizzata per casi semplici quando è richiesto un valore predefinito. Nell’esempio seguente, se l’espressione che utilizza l’operatore di navigazione protetta ritorna null, viene restituito il valore predefinito “sconosciuto”; Nel caso opposto, l’etichetta viene restituita con la versione disponibile.

String version = computer?.getSoundcard()?.getUSB()?.getVersion() ?: "UNKNOWN";

altre lingue funzionali, come Haskell e Scala, adottano una visione diversa. Haskell include un tipo magnifica, che, fondamentalmente, incapsula un valore opzionale. Un valore del tipo forse può contenere un valore di un determinato tipo o nulla. Non esiste un concetto di riferimento null. Scala ha un costrutto simile chiamato Opzione per incapsulare la presenza o l’assenza di un valore di tipo T. Quindi è necessario verificare in modo esplicito se un valore è presente o assente utilizzando le operazioni disponibili nel tipo di opzione, il che rende il “controllo di NULL” . Non è più possibile “dimenticare di controllare” perché il sistema di tipo non lo consente.Bene, abbiamo deviato un po ‘del soggetto e tutto questo suona abbastanza astratto. Forse si chiedono: “Allora, cosa offre Java 8?”.

opzionale in poche parole

java è 8 include una nuova classe chiamata java.util.optional < T >, ispirato a Haskell e Scala. È una classe che incapsula un valore opzionale, come mostrato nell’elenco 2, incluso di seguito, e nella figura 2. Opzionale può essere considerato un contenitore a valore singolo che o contiene un valore o non lo contiene (in tal caso, è detto Quello è “vuoto”), come mostrato nella Figura 2. Java8 -Obtional- fig.2

figura 2: scheda audio opzionale

Possiamo modificare il nostro modello e utilizzare facoltativo, come osservato nell’elenco 2:

public class Computer { private Optional<Soundcard> soundcard; public Optional<Soundcard> getSoundcard() { ... } ...} public class Soundcard { private Optional<USB> usb; public Optional<USB> getUSB() { ... } } public class USB{ public String getVersion(){ ... }}

elenco 2

Nell’elenco 2, è immediatamente evidente che un computer può o non può avere una scheda audio (la scheda audio è facoltativa). Inoltre, la scheda audio può contestare opzionalmente con la porta USB. È un miglioramento rispetto al modello precedente, poiché questo nuovo modello riflette chiaramente se è possibile assente un determinato valore. Si noti che le possibilità simili sono disponibili nelle biblioteche come Gua. Ma cosa può effettivamente essere fatto con un oggetto opzionale < SoundCard >? Dopo tutto, vuoi ottenere il numero di versione della porta USB. In breve, la classe facoltativa include metodi che consentono esplicitamente a gestire i casi in cui è presente un valore o assente. Tuttavia, il vantaggio rispetto ai riferimenti nullo è che la classe opzionale obbliga a pensare al caso in cui il valore non è presente. Di conseguenza, è possibile prevenire le eccezioni del puntatore NULL non intenzionale. È importante notare che l’obiettivo della classe facoltativo non è quello di sostituire tutti i riferimenti nulli, ma il suo scopo è quello di aiutare a progettare routine API più comprensibili, in modo tale da leggere la firma di un metodo potrebbe essere possibile se il rimborso può essere previsto di un valore opzionale. Se questo è il caso, siamo costretti a “dissuabili” la classe opzionale per agire prima dell’assenza di un valore.

Motivi per l’adozione di optional

Spiegazione sufficiente: Andiamo codice. Innanzitutto, vedremo come riscrivere i tipici modelli di controllo null usando opzionale. A conclusione di questo articolo, saprai come utilizzare facoltativo per riscrivere il codice che mostra l’elenco 1, in cui sono stati eseguiti diversi controlli nidificati nulli:

String name = computer.flatMap(Computer::getSoundcard) .flatMap(Soundcard::getUSB) .map(USB::getVersion) .orElse("UNKNOWN");

Nota: Assicurarsi di rivedere la sintassi dei riferimenti a metodi e espressioni lambda da Java SE8 (vedere “Java 8: Lambdas”) e i concetti di canale (conduttura) da flussi (vedi “I dati di elaborazione con Java sono 8 stream”).

Creazione di oggetti opzionali

Innanzitutto, come vengono creati gli oggetti opzionali? Esistono diverse modalità: questo è un vuoto opzionale:

Optional&lt;Soundcard&gt; sc = Optional.empty();

e questa è un’opzione con un valore diverso da zero:

SoundCard soundcard = new Soundcard();Optional<Soundcard> sc = Optional.of(soundcard); 

Soundcard era null, un’eccezione NullPointerException verrà lanciata immediatamente (invece di ottenere un errore latente durante il tentativo di accedere alle proprietà della SoundCard). Inoltre, utilizzando il furninibile, è possibile creare un oggetto opzionale che può contenere un valore null:

Optional<Soundcard> sc = Optional.ofNullable(soundcard);

Se la soresta è stata nullo, l’oggetto opzionale risultante sarebbe vuoto.

Fai qualcosa prima della presenza di un valore

Ora che abbiamo un oggetto opzionale, possiamo ricorrere ai metodi disponibili per affrontare esplicitamente dalla presenza o dall’assenza di valori. Invece di vederci costretti a ricordare di controllare a Null, come segue:

SoundCard soundcard = ...;if(soundcard != null){ System.out.println(soundcard);}

Possiamo utilizzare il metodo IFSSENT () come Visto sotto:

Optional<Soundcard> soundcard = ...;soundcard.ifPresent(System.out::println);

Non abbiamo più bisogno di eseguire un controllo null esplicito: il sistema dei tipi si occupa di eseguirlo . Se l’oggetto opzionale era vuoto, nulla non sarebbe stato stampato. Possiamo anche utilizzare il metodo ispresent () per scoprire se c’è un valore in un oggetto opzionale. Inoltre, c’è un metodo GET () che restituisce il valore contenuto nell’oggetto opzionale, se applicabile. Altrimenti, getta un’eccezione NoschelementException. È possibile combinare entrambi i metodi per prevenire le eccezioni, come mostrato di seguito:

if(soundcard.isPresent()){ System.out.println(soundcard.get());}

Tuttavia, questo non è questo uso consigliato da facoltativo (non rappresenta un miglioramento significativo rispetto ai controlli nidificati nulli); Inoltre, ci sono più alternative linguistiche, che esploreremo più tardi.

Valori e azioni predefiniti

Un modello tipico consiste nel restituire un valore predefinito se è determinato che il risultato di un’operazione è nullo. In generale, per raggiungere questo obiettivo, è possibile utilizzare l’operatore Ternario:

Soundcard soundcard = maybeSoundcard != null ? maybeSoundcard : new Soundcard("basic_sound_card");

Se viene utilizzato un oggetto opzionale, è usato possibile riscrivere il codice precedente utilizzando il metodo Orelse (), che fornisce un valore predefinito se opzionale è vuoto:

Soundcard soundcard = maybeSoundcard.orElse(new Soundcard("defaut")); 

Allo stesso modo, il metodo OlhelsEthrow () può essere utilizzato, che, invece di fornire un valore predeterminato nel caso in cui facoltativo sia vuoto, lancia un’eccezione:

Soundcard soundcard = maybeSoundCard.orElseThrow(IllegalStateException::new);

rifiuto di Alcuni valori con l’uso del metodo del filtro

Spesso, è necessario chiamare un metodo oggetto e controllare alcune proprietà. Ad esempio, potrebbe essere necessario verificare se la porta USB è una certa versione. Per fare ciò senza rischi, è necessario prima controllare se il riferimento che punta a un oggetto USB è nullo e chiama, quindi, il metodo GetVersion (), come segue:

USB usb = ...;if(usb != null && "3.0".equals(usb.getVersion())){ System.out.println("ok");}

È possibile riscrivere questo schema utilizzando il metodo del filtro per un oggetto opzionale, come mostrato di seguito:

Optional<USB> maybeUSB = ...;maybeUSB.filter(usb -> "3.0".equals(usb.getVersion()) .ifPresent(() -> System.out.println("ok"));

Il metodo del filtro richiede un predicato come argomento. Se c’è un valore nell’oggetto opzionale e tale valore è conforme al predicato, il metodo del filtro restituisce tale valore; Altrimenti, restituisce un oggetto opzionale vuoto. È possibile trovare un modello simile se hai utilizzato il metodo del filtro con l’interfaccia del flusso.

rimozione e trasformazione dei valori con il metodo Map

è un altro modello frequente consiste nell’estrazione informazioni da un oggetto. Ad esempio, potrebbe verificarsi che si desidera estrarre l’oggetto USB da un oggetto e controlla un oggetto della SoundCard, quindi se è dalla versione corretta. Il codice tipico sarebbe:

if(soundcard != null){ USB usb = soundcard.getUSB(); if(usb != null && "3.0".equals(usb.getVersion()){ System.out.println("ok"); }}

è possibile riscrivere questo schema “Controllare null ed estratto” (in questo caso, La soundcard dell’oggetto) utilizzando il metodo della mappa.

Optional<USB> usb = maybeSoundcard.map(Soundcard::getUSB);

C’è un parallelo diretto con il metodo della mappa utilizzato con i flussi. Lì, una funzione viene passata al metodo della mappa, che applica questa funzione a ciascun elemento di un flusso. Tuttavia, se il flusso è vuoto, non succede nulla. Il metodo della mappa della classe facoltativo fa lo stesso: la funzione che viene passata come argomento (in questo caso, un riferimento a un metodo per estrarre la porta USB) “Trasforma” il valore contenuto in facoltativo, mentre nulla accade se facoltativo vuoto. Infine, possiamo combinare il metodo della mappa con il metodo del filtro per rifiutare una porta USB la cui versione non è 3.0:

maybeSoundcard.map(Soundcard::getUSB) .filter(usb -> "3.0".equals(usb.getVersion()) .ifPresent(() -> System.out.println("ok"));

Great : Il nostro codice inizia a affrontare il bersaglio cercato, senza espliciti controlli nulli che sono interposti sulla strada.

Oggetto opzionale Acqua con il metodo flatmap

Abbiamo già visto alcuni modelli che possono essere refatturalizzato per usare facoltativo. Ora, come possiamo scrivere il seguente codice in modalità provvisoria?

String version = computer.getSoundcard().getUSB().getVersion();

Nota che l’unica funzione di questo codice è estrarre un oggetto da un altro, esattamente lo scopo del metodo della mappa. Nelle righe precedenti, modifichiamo il nostro modello in modo che il computer abbia opzionale < e SoundCard ha opzionale < USB >, quindi dovrebbe essere possibile scrivere quanto segue:

String version = computer.map(Computer::getSoundcard) .map(Soundcard::getUSB) .map(USB::getVersion) .orElse("UNKNOWN");

Sfortunatamente, non è possibile compilare questo codice. Perché? La variabile del computer è di tipo opzionale < Computer >, quindi è corretto chiamare il metodo della mappa. Tuttavia, GetsoundCard () restituisce un oggetto tipo opzionale < SoundCard >, il che significa che il risultato dell’operazione della mappa è un oggetto opzionale Digitare < opzionale < SoundCard > > DIV Igienico Di conseguenza, la chiamata a gettusb () non è valida perché l’opzione esterna contiene come valore un’altra opzione che, ovviamente, non supporta il metodo GeTUSB (). La figura 3 illustra la struttura elettorale annidata che si ottiene. java8 -optional- fig.3

Figura 3: un a due livelli opzionale

Come può essere risolto il problema? Ancora una volta, possiamo ricorrere a un modello che forse hai usato prima con i flussi: il metodo flatmap. Con i flussi, il metodo flatmap prende una funzione come argomento, che restituisce un nuovo flusso. Quella funzione viene applicata a ciascun elemento del flusso, che comporterebbe il flusso di flusso.Tuttavia, l’effetto flatmap consiste nella sostituzione di ciascun flusso generato dal contenuto del flusso in questione. In altre parole, tutti i flussi generati dalla funzione sono amalgamani o “Aplanan” in un unico flusso. Ciò di cui abbiamo bisogno in questo caso è qualcosa di simile, ma cerchiamo di “appiattire” un due livelli opzionali e otteniamo, d’altra parte, un unico livello.

Bene, abbiamo buone notizie: opzionale supporta anche un metodo flatmap. Il suo scopo è quello di applicare il processo di trasformazione al valore di un opzionale (come accade con l’operazione della mappa), quindi “appiattire” i due livelli opzionali per ottenere un unico livello. La figura 4 illustra la differenza tra la mappa e la flatmap quando la funzione di trasformazione restituisce un oggetto opzionale.

java8 -optional- fig.4

Figura 4: confronto dal Uso della mappa e flatmap con

opzionale, quindi il codice è corretto, dobbiamo riscriverlo come segue utilizzando flatmap:

String version = computer.flatMap(Computer::getSoundcard) .flatMap(Soundcard::getUSB) .map(USB::getVersion) .orElse("UNKNOWN");

La prima flatmap garantisce che viene restituito opzionale < SoundCard > anziché opzionale < opzionale < SoundCard > > e la seconda flatmap Raggiunge lo stesso obiettivo con il ritorno del < USB >. Si noti che nel caso della terza chiamata, è necessario solo () perché GetVersion () restituisce una stringa anziché un oggetto opzionale.

Grande! Abbiamo fatto un sacco di progressi: siamo andati dalla scrittura di fastidiosi calancieri null per scrivere un codice dichiarativo che è leggibile, supporta la composizione ed è meglio protetto da eccezioni del puntatore null.

conclusione

In questo articolo, abbiamo affrontato l’adozione della nuova classe Java.util.optional < T > Java SE 8. Lo scopo da Facoltativo non menti nel sostituire tutti i riferimenti nulli del codice, ma per aiutare a progettare migliori routine API in cui, leggendo la firma di un metodo, gli utenti sanno se attendere o meno un valore opzionale. Inoltre, obbliga optionalmente “desancapolutazione” un’opzione per agire all’assenza di un valore; Di conseguenza, la presenza di eccezioni di puntatore null non intenzionale è impedito nel codice.

Informazioni aggiuntive

  • Capitolo 9, “Opzionale: una migliore alternativa a null” (Facoltativo, un’alternativa migliore di NULL), in Java 8 in azione: Lambdas, flussi e programmazione in stile funzionale (Java 8 in azione: Lambdas, flussi e programmazione funzionale)
  • “Monadica Java” (Java Monadica ” Java Monadic) di Mario Fusco
  • “Dati di elaborazione con Java 8 Streams” (elaborazione dei dati con flussi Java SE8)

ACCONDICI RICONOSCIMENTI

GRAZIE Alan Mycroft e Mario Fusco per intraprendere la scrittura Java 8 in azione: lambdas, ruscelli e programmazione in stile funzionale insieme a me.

raoul-gabriel urna (@raoulk) sta finendo il suo dottorato in informatica a L’Università di Cambridge, dove sviluppa la sua ricerca nei linguaggi di programmazione. È Coauthor de Java 8 in azione: lambdas, ruscelli e programmazione in stile funzionale, che saranno pubblicati presto maneggiando. Inoltre, di solito partecipa come un espositore presso conferenze su Java di prima linea (ad esempio Devoxx e Fosdem) e viene effettuata come istruttore. Ha anche lavorato in diverse aziende prestigiose, incluso il team di Google Python, il gruppo di piattaforme Java di Oracle, Ebay e Goldman Sachs, nonché in diversi progetti di nuove imprese.

Leave a Comment

Il tuo indirizzo email non sarà pubblicato. I campi obbligatori sono contrassegnati *