Undtagelser i Java 8 Lambda Expressions

1. Oversigt

I Java 8 begyndte Lambda Expressions at lette funktionel programmering ved at give en kortfattet måde at udtrykke adfærd på. Men den Funktionelle grænseflader leveret af JDK håndterer ikke undtagelser særlig godt - og koden bliver detaljeret og besværlig, når det kommer til at håndtere dem.

I denne artikel undersøger vi nogle måder at håndtere undtagelser på, når vi skriver lambda-udtryk.

2. Håndtering af ukontrollerede undtagelser

Lad os først forstå problemet med et eksempel.

Vi har en Liste og vi vil dele en konstant, siger 50 med hvert element på denne liste og udskrive resultaterne:

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

Dette udtryk fungerer, men der er et problem. Hvis nogle af elementerne på listen er 0, så får vi en ArithmeticException: / ved nul. Lad os ordne det ved hjælp af en traditionel prøve-fangst blokere sådan, at vi logger en sådan undtagelse og fortsætter udførelsen af ​​de næste elementer:

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

Brugen af prøve-fangst løser problemet, men kortfattetheden af ​​a Lambda-udtryk går tabt, og det er ikke længere en lille funktion, som det skulle være.

For at løse dette problem kan vi skrive en lambdaindpakning til lambda-funktionen. Lad os se på koden for at se, hvordan den fungerer:

statisk Forbruger lambdaWrapper (Forbrugerforbruger) {return i -> {prøv {forbruger.accept (i); } fange (ArithmeticException e) {System.err.println ("Arithmetic Exception occured:" + e.getMessage ()); }}; }
Liste heltal = Arrays.asList (3, 9, 7, 0, 10, 20); heltal.forEach (lambdaWrapper (i -> System.out.println (50 / i)));

Først skrev vi en indpakningsmetode, der er ansvarlig for håndtering af undtagelsen og derefter overførte lambda-udtrykket som en parameter til denne metode.

Indpakningsmetoden fungerer som forventet, men du kan argumentere for, at det grundlæggende fjerner prøve-fangst blokere fra lambda-udtryk og flytte det til en anden metode, og det reducerer ikke det faktiske antal linjer med kode, der skrives.

Dette gælder i dette tilfælde, hvor indpakningen er specifik for en bestemt brugssag, men vi kan bruge generiske stoffer til at forbedre denne metode og bruge den til en række andre scenarier:

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

Som vi kan se, tager denne iteration af vores indpakningsmetode to argumenter, lambda-udtrykket og typen af Undtagelse at blive fanget. Denne lambda-indpakning er i stand til at håndtere alle datatyper, ikke kun Heltal, og fange en hvilken som helst specifik undtagelse og ikke superklassen Undtagelse.

Bemærk også, at vi har ændret metodenavnet fra lambdaWrapper til forbrugerindpakning. Det er fordi denne metode kun håndterer lambda-udtryk for Funktionelt interface af typen Forbruger. Vi kan skrive lignende indpakningsmetoder til andre funktionelle grænseflader som f.eks Fungere, BiFunction, BiConsumer og så videre.

3. Håndtering af kontrollerede undtagelser

Lad os ændre eksemplet fra det foregående afsnit, og i stedet for at udskrive til konsollen, lad os skrive til en fil.

statisk ugyldig writeToFile (heltal) kaster IOException {// logik til at skrive til fil, der kaster IOException}

Bemærk, at ovenstående metode kan kaste IOUndtagelse.

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

Ved kompilering får vi fejlen:

java.lang.Error: Uopklaret kompileringsproblem: Uhåndteret undtagelsestype IOException

Fordi IOUndtagelse er en kontrolleret undtagelse, skal vi håndtere det eksplicit. Vi har to muligheder.

For det første kan vi bare smide undtagelsen uden for vores metode og tage os af den et andet sted.

Alternativt kan vi håndtere det inde i metoden, der bruger et lambda-udtryk.

Lad os undersøge begge muligheder.

3.1. Kaster afkrydset undtagelse fra Lambda Expressions

Lad os se, hvad der sker, når vi erklærer IOUndtagelse på den vigtigste metode:

offentlig statisk ugyldig hoved (String [] args) kaster IOException {List heltal = Arrays.asList (3, 9, 7, 0, 10, 20); heltal.forEach (i -> writeToFile (i)); }

Stadig, vi får den samme fejl ved uhåndteret IOUndtagelse under kompilering.

java.lang.Error: Uopklaret kompileringsproblem: Uhåndteret undtagelsestype IOException

Dette skyldes, at lambda-udtryk ligner anonyme indre klasser.

I vores tilfælde skriv til fil metoden er implementeringen af Forbruger funktionel grænseflade.

Lad os se på Forbruger'S definition:

@FunctionalInterface offentlig grænseflade Forbruger {ugyldig accept (T t); }

Som vi kan se acceptere metode erklærer ikke nogen kontrolleret undtagelse. Det er derfor skriv til fil må ikke smide IOUndtagelse.

Den mest ligefremme måde ville være at bruge en prøve-fangst blok, pakk den afkrydsede undtagelse ind i en ikke-markeret undtagelse og kast den igen:

Liste heltal = 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 at kompilere og køre. Denne tilgang introducerer imidlertid det samme spørgsmål, som vi allerede diskuterede i det foregående afsnit - det er detaljeret og besværligt.

Vi kan blive bedre end det.

Lad os oprette en brugerdefineret funktionel grænseflade med en enkelt acceptere metode, der kaster en undtagelse.

@FunctionalInterface offentlig grænseflade ThrowingConsumer {ugyldig accept (T t) kaster E; }

Og lad os nu implementere en indpakningsmetode, der er i stand til at omlægge undtagelsen:

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

Endelig er vi i stand til at forenkle den måde, vi bruger skriv til fil metode:

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

Dette er stadig en slags løsning, men slutresultatet ser ret rent ud og er bestemt lettere at vedligeholde.

Begge, den Kaster forbruger og kasteConsumerWrapper er generiske og kan let genbruges forskellige steder i vores ansøgning.

3.2. Håndtering af en kontrolleret undtagelse i Lambda-udtryk

I dette sidste afsnit ændrer vi indpakningen for at håndtere afkrydsede undtagelser.

Siden vores Kaster forbruger bruger grænseflade, kan vi nemt håndtere enhver specifik undtagelse.

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

Lad os se, hvordan man bruger det i praksis:

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

Bemærk, at ovenstående kode kun håndtag IO-undtagelse, der henviser til, at enhver anden form for undtagelse omlægges som en RuntimeException .

4. Konklusion

I denne artikel viste vi, hvordan man håndterer en bestemt undtagelse i lambda-udtryk uden at miste kortfattetheden ved hjælp af indpakningsmetoder. Vi lærte også, hvordan man skriver kastealternativer til de funktionelle grænseflader, der findes i JDK, for enten at kaste eller håndtere en kontrolleret undtagelse.

En anden måde ville være at udforske sneaky-throw-hacket.

Den komplette kildekode til Functional Interface og wrapper-metoder kan downloades herfra og testklasser herfra over på Github.

Hvis du er på udkig efter out-of-the-box arbejdsløsninger, er ThrowingFunction-projektet værd at tjekke ud.