Unntak i Java 8 Lambda Expressions

1. Oversikt

I Java 8 begynte Lambda Expressions å legge til rette for funksjonell programmering ved å gi en kortfattet måte å uttrykke atferd på. Imidlertid, den Funksjonelle grensesnitt gitt av JDK håndterer ikke unntak veldig bra - og koden blir ordentlig og tungvint når det gjelder håndtering av dem.

I denne artikkelen vil vi utforske noen måter å håndtere unntak når du skriver lambdauttrykk.

2. Håndtering av ukontrollerte unntak

La oss først forstå problemet med et eksempel.

Vi har en Liste og vi vil dele en konstant, si 50 med hvert element i denne listen og skrive ut resultatene:

Liste heltall = Arrays.asList (3, 9, 7, 6, 10, 20); heltall.forEach (i -> System.out.println (50 / i));

Dette uttrykket fungerer, men det er ett problem. Hvis noen av elementene i listen er 0, så får vi en ArithmeticException: / ved null. La oss fikse det ved å bruke en tradisjonell prøvefangst blokker slik at vi logger et slikt unntak og fortsetter utførelsen for neste elementer:

Liste heltall = Arrays.asList (3, 9, 7, 0, 10, 20); heltall.forEach (i -> {prøv {System.out.println (50 / i);} fangst (ArithmeticException e) {System.err.println ("Aritmetisk unntak oppstod:" + e.getMessage ());}} );

Bruken av prøvefangst løser problemet, men kortfattetheten til a Lambda-uttrykk er tapt, og det er ikke lenger en liten funksjon som den skal være.

For å takle dette problemet kan vi skrive en lambda-innpakning for lambda-funksjonen. La oss se på koden for å se hvordan den fungerer:

statisk Forbruker lambdaWrapper (Forbrukerforbruker) {return i -> {try {consumer.accept (i); } fangst (ArithmeticException e) {System.err.println ("Aritmetisk unntak oppstod:" + e.getMessage ()); }}; }
Liste heltall = Arrays.asList (3, 9, 7, 0, 10, 20); heltall.forEach (lambdaWrapper (i -> System.out.println (50 / i)));

Først skrev vi en innpakningsmetode som vil være ansvarlig for å håndtere unntaket, og deretter passerte lambdauttrykket som en parameter til denne metoden.

Innpakningsmetoden fungerer som forventet, men du kan hevde at den i utgangspunktet fjerner prøvefangst blokkere fra lambda-uttrykk og flytte det til en annen metode, og det reduserer ikke det faktiske antall kodelinjer som skrives.

Dette gjelder i dette tilfellet der innpakningen er spesifikk for en bestemt brukssak, men vi kan bruke generiske stoffer for å forbedre denne metoden og bruke den til en rekke andre scenarier:

statisk Consumer consumerWrapper (Consumer consumer, Class clazz) {return i -> {try {consumer.accept (i); } fange (Unntak ex) {prøv {E exCast = clazz.cast (ex); System.err.println ("Unntak skjedde:" + exCast.getMessage ()); } fange (ClassCastException ccEx) {kaste eks; }}}; }
Liste heltall = Arrays.asList (3, 9, 7, 0, 10, 20); heltall.forEach (consumerWrapper (i -> System.out.println (50 / i), ArithmeticException.class));

Som vi kan se, tar denne iterasjonen av innpakningsmetoden vår to argumenter, lambdauttrykket og typen Unntak å bli tatt. Denne lambda-innpakningen er i stand til å håndtere alle datatyper, ikke bare Heltall, og ta en bestemt type unntak og ikke superklassen Unntak.

Legg også merke til at vi har endret navnet på metoden fra lambdaWrapper til consumerWrapper. Det er fordi denne metoden bare håndterer lambdauttrykk for Funksjonelt grensesnitt av typen Forbruker. Vi kan skrive lignende innpakningsmetoder for andre funksjonelle grensesnitt som Funksjon, BiFunction, BiConsumer og så videre.

3. Håndtering av sjekket unntak

La oss endre eksemplet fra forrige avsnitt, og i stedet for å skrive ut til konsollen, la oss skrive til en fil.

statisk ugyldig writeToFile (heltall) kaster IOException {// logikk for å skrive til fil som kaster IOException}

Merk at metoden ovenfor kan kaste IO Unntak.

Liste heltall = Arrays.asList (3, 9, 7, 0, 10, 20); heltall.forEach (i -> writeToFile (i));

Ved kompilering får vi feilen:

java.lang.Error: Problem med ikke-løst kompilering: Ubehandlet unntakstype IOException

Fordi IO Unntak er et sjekket unntak, må vi håndtere det eksplisitt. Vi har to alternativer.

For det første kan vi bare kaste unntaket utenfor metoden vår og ta vare på det et annet sted.

Alternativt kan vi håndtere det inne i metoden som bruker et lambdauttrykk.

La oss utforske begge alternativene.

3.1. Kaster avkrysset unntak fra Lambda Expressions

La oss se hva som skjer når vi erklærer IO Unntakhoved- metode:

public static void main (String [] args) kaster IOException {List heltall = Arrays.asList (3, 9, 7, 0, 10, 20); heltall.forEach (i -> writeToFile (i)); }

Fortsatt, vi får den samme feilen av ubehandlet IO Unntak under samlingen.

java.lang.Error: Problem med uløst kompilering: Ubehandlet unntakstype IOException

Dette er fordi lambdauttrykk ligner på anonyme indre klasser.

I vårt tilfelle, writeToFile metoden er implementeringen av Forbruker funksjonelt grensesnitt.

La oss ta en titt på ForbrukerSin definisjon:

@FunctionalInterface public interface Consumer {void accept (T t); }

Som vi kan se aksepterer metoden erklærer ikke noe avkrysset unntak. Dette er grunnen writeToFile har ikke lov til å kaste IO Unntak.

Den enkleste måten ville være å bruke en prøvefangst blokker, pakk det avmerkede unntaket inn i et ukontrollert unntak og kast det på nytt:

Liste heltall = Arrays.asList (3, 9, 7, 0, 10, 20); integers.forEach (i -> {try {writeToFile (i);} catch (IOException e) {throw new RuntimeException (e);}}); 

Dette får koden til å kompilere og kjøre. Imidlertid introduserer denne tilnærmingen det samme problemet som vi allerede diskuterte i forrige avsnitt - det er ordentlig og tungvint.

Vi kan bli bedre enn det.

La oss lage et tilpasset funksjonelt grensesnitt med en enkelt aksepterer metode som kaster et unntak.

@FunctionalInterface offentlig grensesnitt ThrowingConsumer {void accept (T t) kaster E; }

La oss nå implementere en innpakningsmetode som er i stand til å omgjøre unntaket:

statisk Consumer throwingConsumerWrapper (ThrowingConsumer throwingConsumer) {return i -> {try {throwingConsumer.accept (i); } fange (Unntak ex) {kast ny RuntimeException (ex); }}; }

Endelig er vi i stand til å forenkle måten vi bruker writeToFile metode:

Liste heltall = Arrays.asList (3, 9, 7, 0, 10, 20); heltall.forEach (throwingConsumerWrapper (i -> writeToFile (i)));

Dette er fortsatt en slags løsning, men sluttresultatet ser ganske rent ut og er definitivt lettere å vedlikeholde.

Begge Kaster forbruker og kasterConsumerWrapper er generiske og kan enkelt brukes på forskjellige steder i applikasjonen vår.

3.2. Håndtering av et avkrysset unntak i Lambda-uttrykk

I denne siste delen vil vi endre innpakningen for å håndtere avmerkede unntak.

Siden vår Kaster forbruker bruker grensesnitt, kan vi enkelt håndtere ethvert unntak.

statisk forbrukerhåndteringConsumerWrapper (ThrowingConsumer throwingConsumer, Class exceptionClass) {return i -> {try {throwingConsumer.accept (i); } fange (Unntak ex) {prøv {E exCast = exceptionClass.cast (ex); System.err.println ("Unntak skjedde:" + exCast.getMessage ()); } fange (ClassCastException ccEx) {kast ny RuntimeException (ex); }}}; }

La oss se hvordan du bruker det i praksis:

Liste heltall = Arrays.asList (3, 9, 7, 0, 10, 20); integers.forEach (handlingConsumerWrapper (i -> writeToFile (i), IOException.class));

Merk at ovennevnte kode bare håndtak IO unntak, mens enhver annen form for unntak blir gjenfunnet som en RuntimeException .

4. Konklusjon

I denne artikkelen viste vi hvordan vi skal håndtere et spesifikt unntak i lambdauttrykk uten å miste konsisensen ved hjelp av innpakningsmetoder. Vi lærte også hvordan vi kan skrive kastealternativer for funksjonelle grensesnitt som er tilstede i JDK for å kaste eller håndtere et avkrysset unntak.

En annen måte ville være å utforske hacket.

Den komplette kildekoden til Functional Interface og wrapper-metoder kan lastes ned herfra og testklasser herfra, over på Github.

Hvis du er ute etter løsninger som ikke er i boksen, er ThrowingFunction-prosjektet verdt å sjekke ut.


$config[zx-auto] not found$config[zx-overlay] not found