Obosit de excepții de pointer nul? Evaluați posibilitatea utilizării Java opțională SE8.

Un om înțelept a spus că odată ce un adevărat programator Java nu sa confruntat cu o excepție de pointer nul. Bancile deoparte, referința nulă dă naștere la multe probleme, deoarece este adesea folosit pentru a desemna absența unei valori. Java SE8 prezintă o nouă clasă numită java.util.opțional care poate atenua unele dintre aceste probleme.

Să începem cu un exemplu pentru a vedea pericolele de utilizare nulă. Gândiți-vă, de exemplu, într-o structură de obiecte imbricate pentru a reprezenta computerul, așa cum este ilustrat în figura 1.

Java8 -Option - Fig.1

Figura 1: Structura imbricată pentru a reprezenta computerul

Ce probleme pot prezenta următorul cod?

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

codul pare destul de logic. Cu toate acestea, multe computere (de exemplu, Raspberry Pi) sunt distribuite fără placă de sunet. Deci, care este rezultatul lui IssoundCard ()?

O practică comună este de a returna referința nulă pentru a indica absența plăcii de sunet. Din păcate, asta înseamnă că apelul la Getusb () va încerca să returneze portul USB al unei referințe null; În consecință, o excepție de nulpointerexcepție va fi aruncată la timpul de execuție, iar programul se va opri să funcționeze. Imaginați-vă că programul dvs. a fost difuzat pe echipa unui client: Ce ar spune clientul dacă programul, brusc, a eșuat?

pentru a oferi un mic context istoric, Tony Houry-One de Giants de la informatică științifică- El a scris: „Îmi sun greșeala de miliarde de dolari: invenția de referință nulă în 1965. Nu am putut rezista tentației de a introduce o referință nulă. A fost atât de ușor să o implementați …”.

Ce se poate face pentru a evita excepțiile non-intenționate ale pointerului nul? O atitudine defensivă poate fi adoptată și adăugarea de verificări pentru a evita referințele null, așa cum se arată în listarea 1:

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

Lista 1

Cu toate acestea, este ușor să vedeți că imediat lista 1 începe să piardă eleganța datorită verificărilor imbricate. Din păcate, avem nevoie de o mulțime de cod repetitiv pentru a vă asigura că nu obțineți o eroare de nullpointerexcepția. În plus, este enervant că aceste verificări interferează cu logica afacerii. De fapt, acestea reduc lizibilitatea generală a programului.

Mai mult, este un proces predispus la erori: Ce s-ar întâmpla dacă ați uitat să verificați dacă o proprietate poate fi nulă? În acest articol, voi susține că utilizarea nulă pentru a reprezenta absența unei valori constituie o abordare eronată. Ceea ce avem nevoie este o modalitate mai bună de a modela absența și prezența unei valori.

Pentru a oferi un anumit context de analiză, examinează ce oferă alte limbi de programare.

Sunt alternativele la utilizarea nullului?

limbi precum Groovy au un operator de navigație securizat reprezentat de „?” Pentru a depăși referințele potențial null fără riscuri. (Rețineți că C # va avea, de asemenea, acest operator și că a fost propus să îl includeți în Java 7, deși nu a fost posibil să se facă acest lucru în acea versiune.) Funcționează după cum urmează:

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

În acest caz, o variabilă nulă va fi atribuită o valoare nulă dacă computerul este nul sau GetoundCard () returnează null sau getusb () returve null. Nu este necesar să se introducă condiții complexe imbricate pentru a verifica prezența nulă. În plus, Groovy are, de asemenea, operatorul Elvis „:” (Dacă arătați-l, veți recunoaște faimosul Hairstyle Elvis), care poate fi utilizat pentru cazuri simple atunci când este necesară o valoare implicită. În exemplul următor, dacă expresia care utilizează operatorul de navigație securizată returnează NULL, valoarea implicită „necunoscută” este returnată; În cazul opus, eticheta este returnată cu versiunea disponibilă.

divid id = „81546E9F66”>

alte limbi funcționale, cum ar fi Haskell și Scala, adoptă o viziune diferită. Haskell include un tip poate, poate, în principiu, încapsulează o valoare opțională. O valoare a tipului poate conține o valoare a unui anumit tip sau nimic. Nu există nici un concept de referință nul. Scala are un construct similar numit opțiunea de a încapsula prezența sau absența unei valori de tip T. Apoi este necesar să se verifice în mod explicit dacă o valoare este prezentă sau absentă utilizând operații disponibile în tipul de opțiune, ceea ce face „verificarea nulă” . Nu mai este posibil să „uitați să verificați” deoarece sistemul de tip nu îl permite.Ei bine, am redirecționat puțin despre subiect și toate acestea sună destul de abstract. Poate că se întreabă: „Deci, ce oferă Java 8?

opțional în câteva cuvinte

Java este 8 include o nouă clasă numită java.util.opțional < t >, inspirat de Haskell și Scala. Este o clasă care încapsulează o valoare opțională, așa cum se arată în listarea 2, inclusă mai jos și în figura 2. Opțional poate fi considerat un container cu valoare unică sau conține o valoare sau nu conține (în acest caz, se spune că este „goală”), după cum se arată în figura 2. Java8-Optional - Fig.2

Figura 2: Sunet opțional card

Putem modifica modelul nostru și utilizăm opțional, așa cum se observă în listarea 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(){ ... }}

listing 2

În listarea 2, este imediat evident că un computer poate sau nu poate avea o placă de sunet (placa de sunet este opțională). În plus, placa de sunet poate conta opțional cu portul USB. Este o îmbunătățire cu privire la modelul anterior, deoarece acest nou model reflectă în mod clar dacă o anumită valoare este permisă să fie absentă. Rețineți că există posibilități similare în biblioteci cum ar fi Gua. Dar ceea ce se poate face cu un obiect opțional < Soundcard >? La urma urmei, doriți să obțineți numărul versiunii portului USB. Pe scurt, clasa opțională include metode care permit relaționarea în mod explicit cu cazurile în care o valoare este prezentă sau absentă. Cu toate acestea, avantajul în comparație cu referințele null este că clasa opțională obligă să se gândească la cazul în care valoarea nu este prezentă. Ca o consecință, este posibil să se prevină excepțiile de pointer non-intenționat. Este important de menționat că obiectivul clasei opționale nu este de a înlocui toate referințele null, dar scopul său este de a ajuta la proiectarea rutinelor API mai ușor de înțeles, astfel încât doar citirea semnării unei metode poate fi posibilă dacă rambursarea poate fi de așteptat de o valoare opțională. Dacă este cazul, suntem forțați să „dezvăluie” clasa opțională să acționăm înainte de absența unei valori.

modele pentru adoptarea opțiunilor opționale

Explicație suficientă: Să mergem pentru cod. În primul rând, vom vedea cum să rescrieți modele tipice de verificare care utilizează opțional. La încheierea acestui articol, veți ști cum să utilizați opțional pentru a rescrie codul care afișează listarea 1, în care s-au efectuat mai multe verificări nulitate:

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

Notă: Asigurați-vă că revizuiți sintaxa referințelor la metode și expresii Lambda din Java SE8 (vezi „Java 8: Lambdas”), precum și conceptele de canal (pipeling) din fluxuri (a se vedea „Procesarea datelor cu Java este de 8 fluxuri”).

Crearea obiectelor opționale

În primul rând, cum sunt create obiectele opționale? Există mai multe moduri: acesta este un obiect opțional:

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

și aceasta este o opțiune cu o valoare non-zero:

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

Soundcard a fost nul, o excepție nullpointerexception ar fi aruncată imediat (în loc să obțină o eroare latentă atunci când încercați să accesați proprietățile de sunet). De asemenea, folosind prins, este posibil să se creeze un obiect opțional care să conțină o valoare nulă:Optional<Soundcard> sc = Optional.ofNullable(soundcard);

Dacă agentul de sunet a fost nul, obiectul opțional rezultat ar fi Goliți.

Fă ceva înainte de prezența unei valori

Acum că avem un obiect opțional, putem recurge la metodele disponibile pentru a face față în mod explicit din prezența sau absența valorilor. În loc să ne vedem forțat să ne amintim să verificăm la nul, după cum urmează:

iv id = „b4a0533fd8”

Putem folosi metoda IFPSENT () ca Văzut mai jos:

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

Nu mai trebuie să efectuăm un control nul explicit: sistemul de tipuri pe care îl are grijă să-l execute . Dacă obiectul opțional a fost gol, nimic nu nu va fi tipărit. De asemenea, putem folosi metoda Ispresent () pentru a afla dacă există o valoare într-un obiect opțional. În plus, există o metodă de obținere () care returnează valoarea conținută în obiectul opțional, dacă este cazul. În caz contrar, aruncă o excepție de noschelementexception. Este posibil să se combine ambele metode pentru a preveni excepțiile, după cum se arată mai jos:

div> iv id = „16aee90ff4”

Cu toate acestea, aceasta nu este această utilizare recomandată de la opțional (nu reprezintă o îmbunătățire semnificativă în ceea ce privește controalele nulitate nulă); În plus, există mai multe alternative de limbă, pe care le vom explora mai târziu.

Valori și acțiuni implicite

Un model tipic constă în returnarea unei valori implicite dacă se determină că rezultatul unei operații este nulă. În general, pentru a atinge acest obiectiv, poate fi utilizat operatorul ternar:

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

Dacă este utilizat un obiect opțional, este posibilă rescrieți codul anterior utilizând metoda Orelse (), care oferă o valoare implicită dacă opțional este goală:

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

În mod similar, metoda Orelsethrow () poate Fiți folosit, că, în loc să ofere o valoare predeterminată în cazul în care opțional este goală, ea aruncă o excepție:

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

respingerea Anumite valori cu utilizarea metodei de filtrare

Adesea, este necesar să se apeleze o metodă de obiect și să verifice anumite proprietăți. De exemplu, poate fi necesar să se verifice dacă portul USB este o anumită versiune. Pentru a face acest lucru fără riscuri, este necesar să verificați mai întâi dacă referința care indică un obiect USB este nulă și apelul, atunci, metoda GetVersion (), după cum urmează:

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

Este posibilă rescrierea acestui model utilizând metoda filtrului pentru un obiect opțional, după cum se arată mai jos:

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

Metoda de filtrare ia un predicat ca argument. Dacă există o valoare în obiectul opțional și că valoarea respectă predicatul, metoda filtrului returnează această valoare; În caz contrar, acesta returnează un obiect opțional gol. Este posibil să găsiți un model similar dacă ați utilizat metoda filtrului cu interfața fluxului.

Eliminarea și transformarea valorilor cu metoda

este un alt model frecvent constă din extragerea informații dintr-un obiect. De exemplu, poate apărea că doriți să extrageți obiectul USB de la un obiect de sunet și verificați, atunci dacă acesta este din versiunea corectă. Codul tipic ar fi:

div> iv id = „F9F69F54C2”

Este posibilă rescrierea acestui model „Verificați nul și extractul” (în acest caz, Soundcardul obiect) utilizând metoda hărții.

DIV ID = „469C0F278E”>

Există o paralelă directă cu metoda hărții utilizate cu fluxuri. Acolo, o funcție este transmisă la metoda hărții, care se aplică acelei funcții fiecărui element al unui flux. Cu toate acestea, dacă fluxul este gol, nu se întâmplă nimic. Metoda hărții din clasa opțională face același lucru: funcția care este adoptată ca argument (în acest caz, o referință la o metodă de extragere a portului USB) „transformă” valoarea conținut în opțional, în timp ce nimic nu se întâmplă dacă este opțional gol. În cele din urmă, putem combina metoda hărții cu metoda filtrului pentru a respinge un port USB a cărui versiune nu este 3.0:

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

mare : Codul nostru începe să se apropie de țintă căutată, fără verificări explicite care sunt interpuse pe șosea.

Apa opțională a obiectului cu metoda platMap

Am văzut deja câteva modele pe care le pot să fie refactorizat pentru a utiliza opțional. Acum, cum putem scrie următorul cod de siguranță?

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

rețineți că singura funcție a acestui cod este de a extrage un obiect de la altul, exact scopul scopului din metoda hărții. În liniile anterioare, ne modificăm modelul, astfel încât computerul să fi avut opțional < și carte de sunet au opțional < USB >, deci ar trebui să fie posibilă scrierea următoarelor:

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

Din păcate, nu este posibil să se compileze acest cod. De ce? Variabila computerului este tipul opțional < computer >, deci este corect să apelați metoda hărții. Cu toate acestea, GetoundCard () returnează un obiect de tip opțional < Fad1d8b1f8 „> , ceea ce înseamnă că rezultatul funcționării hărții este un obiect de opțional Introduceți < opțional < Soundcard > iv id = „fad1d8b1f8” Ca o consecință, apelul către Getusb () nu este valabil deoarece opțiunea exterioară conține ca o altă opțiune care, desigur, nu acceptă metoda Getusb (). Figura 3 ilustrează structura electorală imbricată care ar fi obținută. Java8-Optional - Fig.3

Figura 3: O opțională cu două niveluri

Cum poate fi rezolvată problema? Încă o dată, putem recurge la un model care poate fi folosit înainte cu fluxurile: metoda platMap. Cu fluxurile, metoda FlatMap are o funcție ca argument, care returnează un nou flux. Această funcție este aplicată fiecărui element de flux, care ar duce la fluxul de flux.Cu toate acestea, efectul platMap constă în înlocuirea fiecărui flux care este generat de conținutul fluxului în cauză. Cu alte cuvinte, toate fluxurile generate de funcția sunt amalgaman sau „aplanan” într-un singur flux. Ceea ce avem nevoie în acest caz este ceva similar, dar căutăm „aplatizați” un nivel opțional două nivele și obținem, pe de altă parte, un singur nivel.

Ei bine, avem o veste bună: opțional, de asemenea, suports o metodă platmap. Scopul său este de a aplica procesul de transformare la valoarea unui opțional (așa cum se întâmplă cu funcționarea hărții) și apoi „aplatizează” cele două niveluri opționale pentru a obține un singur nivel. Figura 4 ilustrează diferența dintre hărți și platformă atunci când funcția de transformare returnează un obiect opțional.

divid id = „607bf5f50c”> java8 -optională- Fig.4

Figura 4: Comparație de la Utilizarea hărții și platformă cu opțional

Apoi, astfel încât codul este corect, trebuie să-l rescriem după cum urmează folosind platforma:

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

Primele platforme garantează că este returnată opțional < ca carte de sunet > în loc de opțional < Opțional < Fad1d8b1f8″> >, și a doua platmap Realizează același obiectiv cu revenirea opțiunii < USB >. Rețineți că, în cazul celui de-al treilea apel, este necesară numai hartă (), deoarece GetVersion () returnează un șir în loc de un obiect opțional.

Mare! Am făcut o mulțime de progrese: am mers de la scrierea de dame null enervante pentru a scrie un cod declarativ care este lizibil, suportă compoziția și este mai bine protejată de excepțiile pointerului nul.

Concluzie

În acest articol, am abordat adoptarea noii clase java.util.opțional < t > Java SE 8. Scopul de la Opționalul nu se află în înlocuirea tuturor referințelor null ale codului, ci pentru a ajuta la proiectarea rutinelor mai bune API în care, citiți semnătura unei metode, utilizatorii știu dacă doriți sau nu să așteptați o valoare opțională. În plus, opțional opțional obligă o opțiune pentru a acționa în absența unei valori; Ca rezultat, prezența excepțiilor de indicare null non-intenționate este împiedicată în cod.

Informații suplimentare

  • Capitolul 9, „Opțional: o alternativă mai bună la NULL” (Opțional, o alternativă mai bună decât null), în Java 8 în acțiune: Lambdas, fluxuri și programare în stil funcțional (Java 8 în acțiune: Lambdas, fluxuri și programare funcțională)
  • „Monahic Java” ( Java Monahic) de Mario FUSCO
  • „Datele de procesare cu fluxuri Java 8” (prelucrarea datelor cu fluxuri Java SE8)

Mulțumiri

i mulțumesc Alan Mycroft și Mario Fusco pentru a-și îmbarca Java 8 în acțiune: Lambdas, fluxuri și programare în stil funcțional împreună cu mine.

Raoul-Gabriel Urma (@Rouluk) își termină doctoratul în informatică la Universitatea din Cambridge, unde își dezvoltă cercetarea în limbile de programare. Este Coauthor de Java 8 în acțiune: Lambdas, fluxuri și programare în stil funcțional, care vor fi publicate în curând prin Manning. În plus, acesta participă, de obicei, ca exponant la conferințe pe Java de primă linie (de exemplu Devoxx și Fosdem) și se desfășoară ca instructor. El a lucrat, de asemenea, în mai multe companii de prestigiu, inclusiv echipa Google Python, grupul de platformă Java de Oracle, Ebay și Goldman Sachs, precum și în mai multe proiecte de afaceri noi.

Leave a Comment

Adresa ta de email nu va fi publicată. Câmpurile obligatorii sunt marcate cu *