Uitzonderingen in Java 8 Lambda Expressions

1. Overzicht

In Java 8 begon Lambda Expressions functioneel programmeren te vergemakkelijken door een beknopte manier te bieden om gedrag uit te drukken. echter, de Functionele interfaces verstrekt door de JDK kunnen uitzonderingen niet zo goed verwerken - en de code wordt uitgebreid en omslachtig als het gaat om het afhandelen ervan.

In dit artikel zullen we enkele manieren onderzoeken om met uitzonderingen om te gaan bij het schrijven van lambda-expressies.

2. Omgaan met ongecontroleerde uitzonderingen

Laten we eerst het probleem begrijpen met een voorbeeld.

We hebben een Lijst en we willen een constante, zeg 50, delen door elk element van deze lijst en de resultaten afdrukken:

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

Deze uitdrukking werkt, maar er is een probleem. Als een van de elementen in de lijst 0, dan krijgen we een ArithmeticException: / bij nul. Laten we dat oplossen door een traditioneel proberen te vangen blokkeren zodat we dergelijke uitzonderingen loggen en doorgaan met de uitvoering voor de volgende elementen:

Lijst gehele getallen = Arrays.asList (3, 9, 7, 0, 10, 20); integers.forEach (i -> {probeer {System.out.println (50 / i);} catch (ArithmeticException e) {System.err.println ("Arithmetic Exception opgetreden:" + e.getMessage ());}} );

Het gebruik van proberen te vangen lost het probleem op, maar de beknoptheid van een Lambda-expressie is verloren gegaan en het is niet langer een kleine functie zoals het hoort te zijn.

Om dit probleem op te lossen, kunnen we schrijven een lambda-wrapper voor de lambda-functie. Laten we naar de code kijken om te zien hoe het werkt:

static Consumer lambdaWrapper (Consumer consumer) {return i -> {try {consumer.accept (i); } catch (ArithmeticException e) {System.err.println ("Rekenkundige uitzondering opgetreden:" + e.getMessage ()); }}; }
Lijst gehele getallen = Arrays.asList (3, 9, 7, 0, 10, 20); integers.forEach (lambdaWrapper (i -> System.out.println (50 / i)));

In eerste instantie hebben we een wrapper-methode geschreven die verantwoordelijk is voor het afhandelen van de uitzondering en vervolgens de lambda-expressie als parameter aan deze methode doorgegeven.

De wrapper-methode werkt zoals verwacht, maar je zou kunnen zeggen dat het in feite het proberen te vangen blok van lambda-expressie en verplaats het naar een andere methode en het vermindert niet het werkelijke aantal regels code dat wordt geschreven.

Dit is waar in dit geval waar de wrapper specifiek is voor een bepaald gebruik, maar we kunnen gebruikmaken van generieke geneesmiddelen om deze methode te verbeteren en deze voor een groot aantal andere scenario's te gebruiken:

statische Consumer consumerWrapper (Consumer consumer, Class clazz) {return i -> {try {consumer.accept (i); } catch (Exception ex) {try {E exCast = clazz.cast (ex); System.err.println ("Uitzondering opgetreden:" + exCast.getMessage ()); } catch (ClassCastException ccEx) {throw ex; }}}; }
Lijst gehele getallen = Arrays.asList (3, 9, 7, 0, 10, 20); gehele getallen.forEach (consumerWrapper (i -> System.out.println (50 / i), ArithmeticException.class));

Zoals we kunnen zien, heeft deze iteratie van onze wrapper-methode twee argumenten nodig, de lambda-uitdrukking en het type Uitzondering gevangen worden. Deze lambda-wrapper kan alle gegevenstypen verwerken, niet alleen Gehele getallen, en vang elk specifiek type uitzondering op en niet de superklasse Uitzondering.

Merk ook op dat we de naam van de methode hebben gewijzigd van lambdaWrapper naar consumerWrapper. Het is omdat deze methode alleen lambda-expressies afhandelt voor Functionele interface van het type Klant. We kunnen vergelijkbare wrapper-methoden schrijven voor andere functionele interfaces, zoals Functie, BiFunction, BiConsumer enzovoorts.

3. Omgaan met gecontroleerde uitzonderingen

Laten we het voorbeeld uit de vorige sectie aanpassen en in plaats van naar de console af te drukken, naar een bestand schrijven.

static void writeToFile (Integer integer) gooit IOException {// logica om naar een bestand te schrijven dat IOException genereert}

Merk op dat de bovenstaande methode de IOException.

Lijst gehele getallen = Arrays.asList (3, 9, 7, 0, 10, 20); gehele getallen.forEach (i -> writeToFile (i));

Bij compilatie krijgen we de foutmelding:

java.lang.Error: onopgelost compilatieprobleem: onverwerkte uitzonderingstype IOException

Omdat IOException is een aangevinkte uitzondering, we moeten er expliciet mee omgaan. We hebben twee mogelijkheden.

Ten eerste kunnen we de uitzondering gewoon buiten onze methode gooien en er ergens anders voor zorgen.

Als alternatief kunnen we het afhandelen binnen de methode die een lambda-uitdrukking gebruikt.

Laten we beide opties bekijken.

3.1. Gecontroleerde uitzondering van Lambda-expressies gooien

Laten we eens kijken wat er gebeurt als we het IOException op de hoofd methode:

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

Nog steeds, we krijgen dezelfde fout als niet-afgehandeld IOException tijdens de compilatie.

java.lang.Error: onopgelost compilatieprobleem: onverwerkte uitzonderingstype IOException

Dit komt doordat lambda-uitdrukkingen vergelijkbaar zijn met anonieme innerlijke klassen.

In ons geval, writeToFile methode is de implementatie van Klant functionele interface.

Laten we eens kijken naar de Klant‘S definitie:

@FunctionalInterface openbare interface Consument {ongeldig accepteren (T t); }

Zoals we kunnen zien aanvaarden methode declareert geen aangevinkte uitzondering. Dit is waarom writeToFile mag de IOException.

De meest eenvoudige manier zou zijn om een proberen te vangen block, verpak de gecontroleerde uitzondering in een niet-gecontroleerde uitzondering en gooi deze opnieuw:

Lijst gehele getallen = Arrays.asList (3, 9, 7, 0, 10, 20); integers.forEach (i -> {probeer {writeToFile (i);} catch (IOException e) {throw nieuwe RuntimeException (e);}}); 

Dit zorgt ervoor dat de code wordt gecompileerd en uitgevoerd. Deze benadering introduceert echter hetzelfde probleem dat we al in de vorige sectie hebben besproken: het is uitgebreid en omslachtig.

We kunnen beter worden dan dat.

Laten we een aangepaste functionele interface maken met een enkele aanvaarden methode die een uitzondering genereert.

@FunctionalInterface openbare interface ThrowingConsumer {void accept (T t) gooit E; }

En laten we nu een wrapper-methode implementeren die de uitzondering opnieuw kan gooien:

statische Consumer throwingConsumerWrapper (ThrowingConsumer throwingConsumer) {return i -> {probeer {throwingConsumer.accept (i); } catch (Exception ex) {throw new RuntimeException (ex); }}; }

Eindelijk kunnen we de manier waarop we het writeToFile methode:

Lijst gehele getallen = Arrays.asList (3, 9, 7, 0, 10, 20); integers.forEach (throwingConsumerWrapper (i -> writeToFile (i)));

Dit is nog steeds een soort tijdelijke oplossing, maar het eindresultaat ziet er mooi schoon uit en is zeker gemakkelijker te onderhouden.

Beide ThrowingConsumer en de throwingConsumerWrapper zijn generiek en kunnen gemakkelijk worden hergebruikt op verschillende plaatsen in onze applicatie.

3.2. Omgaan met een gecontroleerde uitzondering in Lambda Expression

In deze laatste sectie zullen we de wrapper aanpassen om aangevinkte uitzonderingen af ​​te handelen.

Sinds onze ThrowingConsumer interface gebruikt generieke geneesmiddelen, we kunnen gemakkelijk elke specifieke uitzondering afhandelen.

statische Consumer handlingConsumerWrapper (ThrowingConsumer throwingConsumer, Class exceptionClass) {return i -> {probeer {throwingConsumer.accept (i); } catch (Exception ex) {probeer {E exCast = exceptionClass.cast (ex); System.err.println ("Uitzondering opgetreden:" + exCast.getMessage ()); } catch (ClassCastException ccEx) {gooi nieuwe RuntimeException (ex); }}}; }

Laten we eens kijken hoe we het in de praktijk kunnen gebruiken:

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

Merk op dat de bovenstaande code alleen handvatten IOException, terwijl elke andere soort uitzondering opnieuw wordt gegooid als een RuntimeException .

4. Conclusie

In dit artikel hebben we laten zien hoe je een specifieke uitzondering in lambda-expressie kunt afhandelen zonder de beknoptheid te verliezen met behulp van wrapper-methoden. We hebben ook geleerd hoe we throwing-alternatieven kunnen schrijven voor de functionele interfaces die aanwezig zijn in JDK om een ​​aangevinkte uitzondering te gooien of af te handelen.

Een andere manier zou zijn om de sneaky-throws-hack te verkennen.

De volledige broncode van functionele interface en wrapper-methoden kan vanaf hier worden gedownload en testklassen vanaf hier, op Github.

Als u op zoek bent naar kant-en-klare werkoplossingen, is het ThrowingFunction-project de moeite van het bekijken waard.


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