Výnimky vo výrazoch Java 8 Lambda Expressions

1. Prehľad

V prostredí Java 8 začala Lambda Expressions uľahčovať funkčné programovanie poskytnutím stručného spôsobu vyjadrovania správania. Avšak Funkčné rozhrania poskytované JDK veľmi dobre neriešia výnimky - a ich manipulácia s kódom sa stáva verbálnym a ťažkopádnym.

V tomto článku preskúmame niektoré spôsoby riešenia výnimiek pri písaní výrazov lambda.

2. Riešenie nekontrolovaných výnimiek

Po prvé, poďme pochopiť problém na príklade.

Máme Zoznam a chceme rozdeliť konštantu, povedzme 50, na každý prvok tohto zoznamu a vytlačiť výsledky:

Zoznam celých čísel = Arrays.asList (3, 9, 7, 6, 10, 20); integers.forEach (i -> System.out.println (50 / i));

Tento výraz funguje, ale je tu jeden problém. Ak niektorý z prvkov v zozname je 0, potom dostaneme Aritmetická výnimka: / o nulu. Opravíme to pomocou tradičného Skús chytiť zablokujte tak, aby sme zaznamenali každú takúto výnimku a pokračovali v vykonávaní ďalších prvkov:

Zoznam celých čísel = Arrays.asList (3, 9, 7, 0, 10, 20); integers.forEach (i -> {try {System.out.println (50 / i);} catch (ArithmeticException e) {System.err.println ("Vyskytla sa aritmetická výnimka:" + e.getMessage ());}} );

Použitie Skús chytiť rieši problém, ale výstižnosť a Lambda výraz sa stratil a už to nie je malá funkcia, ako by mala byť.

Na riešenie tohto problému môžeme napísať lambda obal pre funkciu lambda. Pozrime sa na kód, aby sme zistili, ako to funguje:

static Consumer lambdaWrapper (Consumer consumer) {return i -> {try {consumer.accept (i); } catch (ArithmeticException e) {System.err.println ("Vyskytla sa aritmetická výnimka:" + e.getMessage ()); }}; }
Zoznam celých čísel = Arrays.asList (3, 9, 7, 0, 10, 20); integers.forEach (lambdaWrapper (i -> System.out.println (50 / i)));

Najprv sme napísali obalovú metódu, ktorá bude zodpovedná za spracovanie výnimky, a potom sme tejto metóde odovzdali výraz lambda ako parameter.

Metóda wrapper funguje podľa očakávania, ale môžete tvrdiť, že v zásade ide o odstránenie súboru Skús chytiť blokuje výraz lambda a presúva ho na inú metódu a neznižuje skutočný počet riadkov zapisovaného kódu.

To platí v tomto prípade, keď je obálka špecifická pre konkrétny prípad použitia, ale môžeme použiť generiku na vylepšenie tejto metódy a použiť ju na rôzne ďalšie scenáre:

static Consumer consumerWrapper (Consumer consumer, Class clazz) {return i -> {try {consumer.accept (i); } catch (Exception ex) {try {E exCast = clazz.cast (ex); System.err.println ("Vyskytla sa výnimka:" + exCast.getMessage ()); } catch (ClassCastException ccEx) {hod ex; }}}; }
Zoznam celých čísel = Arrays.asList (3, 9, 7, 0, 10, 20); integers.forEach (consumerWrapper (i -> System.out.println (50 / i), ArithmeticException.class));

Ako vidíme, táto iterácia našej metódy wrapper vyžaduje dva argumenty, výraz lambda a typ Výnimka byť chytený. Tento obal lambda je schopný spracovať nielen všetky dátové typy Celé číslaa chytiť akýkoľvek konkrétny typ výnimky, nie nadtriedu Výnimka.

Všimnite si tiež, že sme zmenili názov metódy z lambdaWrapper do consumerWrapper. Je to preto, že táto metóda spracováva iba výrazy lambda pre Funkčné rozhranie typu Spotrebiteľ. Môžeme napísať podobné obalové metódy aj pre ďalšie funkčné rozhrania Funkcia, BiFunkcia, BiConsumer a tak ďalej.

3. Zaobchádzanie so zaškrtnutými výnimkami

Upravme príklad z predchádzajúcej časti a namiesto tlače na konzolu zapíšme do súboru.

static void writeToFile (Integer integer) vyvolá IOException {// logiku pre zápis do súboru, ktorý vyvolá IOException}

Uvedomte si, že vyššie uvedená metóda môže spôsobiť Výnimka IO.

Zoznam celých čísel = Arrays.asList (3, 9, 7, 0, 10, 20); integers.forEach (i -> writeToFile (i));

Pri kompilácii sa zobrazí chyba:

java.lang.Error: Nevyriešený problém s kompiláciou: Nespracovaná výnimka typu IOException

Pretože Výnimka IO je kontrolovaná výnimka, musíme s ňou zaobchádzať výslovne. Máme dve možnosti.

Po prvé, môžeme jednoducho vyhodiť výnimku mimo našu metódu a postarať sa o ňu niekde inde.

Prípadne to môžeme spracovať vo vnútri metódy, ktorá používa výraz lambda.

Poďme preskúmať obe možnosti.

3.1. Vrhanie začiarknutej výnimky z výrazov Lambda

Pozrime sa, čo sa stane, keď vyhlásime Výnimka IO na hlavný metóda:

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

Napriek tomu dostaneme rovnakú chybu neošetreného Výnimka IO počas kompilácie.

java.lang.Error: Nevyriešený problém s kompiláciou: Nespracovaná výnimka typu IOException

Je to tak preto, lebo výrazy lambda sú podobné anonymným vnútorným triedam.

V našom prípade writeToFile metóda je implementácia Spotrebiteľ funkčné rozhranie.

Poďme sa pozrieť na SpotrebiteľDefinícia:

@FunctionalInterface verejné rozhranie Consumer {void accept (T t); }

Ako vidíme súhlasiť metóda nedeklaruje žiadnu kontrolovanú výnimku. To je dôvod, prečo writeToFile nesmie hádzať Výnimka IO.

Najpriamejšou cestou by bolo použitie a Skús chytiť blok, zalomte začiarknutú výnimku do nezačiarknutej výnimky a znova ju vytvorte:

Zoznam celých čísel = Arrays.asList (3, 9, 7, 0, 10, 20); integers.forEach (i -> {try {writeToFile (i);} catch (IOException e) {throw new RuntimeException (e);}}); 

Takto sa získa kód na kompiláciu a spustenie. Tento prístup však zavádza tú istú otázku, o ktorej sme už hovorili v predchádzajúcej časti - je to podrobná a ťažkopádna.

Môžeme sa mať lepšie.

Vytvorme vlastné funkčné rozhranie s jedným súhlasiť metóda, ktorá vyvolá výnimku.

@FunctionalInterface verejné rozhranie ThrowingConsumer {void accept (T t) hodí E; }

A teraz poďme implementovať obalovú metódu, ktorá dokáže znova vyvolať výnimku:

statický spotrebiteľ throwingConsumerWrapper (ThrowingConsumer throwingConsumer) {return i -> {try {throwingConsumer.accept (i); } catch (Exception ex) {throw new RuntimeException (ex); }}; }

Nakoniec sme schopní zjednodušiť spôsob, akým používame writeToFile metóda:

Zoznam celých čísel = Arrays.asList (3, 9, 7, 0, 10, 20); integers.forEach (throwingConsumerWrapper (i -> writeToFile (i)));

Toto je stále akési riešenie, ale konečný výsledok vyzerá celkom čisto a jeho údržba je určite ľahšia.

Obidve ThrowingConsumer a throwingConsumerWrapper sú všeobecné a dajú sa ľahko znovu použiť na rôznych miestach našej aplikácie.

3.2. Spracovanie skontrolovanej výnimky vo výraze lambda

V tejto poslednej časti upravíme súhrnný program tak, aby spracovával zaškrtnuté výnimky.

Od nášho ThrowingConsumer rozhranie používa všeobecné, ľahko zvládneme každú konkrétnu výnimku.

static Consumer handlingConsumerWrapper (ThrowingConsumer throwingConsumer, Class exceptionClass) {return i -> {try {throwingConsumer.accept (i); } catch (Exception ex) {try {E exCast = exceptionClass.cast (ex); System.err.println ("Vyskytla sa výnimka:" + exCast.getMessage ()); } catch (ClassCastException ccEx) {throw new RuntimeException (ex); }}}; }

Pozrime sa, ako to v praxi využiť:

Zoznam celých čísel = Arrays.asList (3, 9, 7, 0, 10, 20); integers.forEach (handlingConsumerWrapper (i -> writeToFile (i), IOException.class));

Upozorňujeme, že vyššie uvedený kód iba rúčky IOException, zatiaľ čo akýkoľvek iný druh výnimky sa premení na a RuntimeException .

4. Záver

V tomto článku sme si ukázali, ako zvládnuť konkrétnu výnimku vo výraze lambda bez straty stručnosti pomocou metód wrapper. Tiež sme sa naučili, ako napísať alternatívy hádzania pre funkčné rozhrania prítomné v JDK, aby sme vyvolali alebo spracovali zaškrtnutú výnimku.

Ďalším spôsobom by bolo preskúmať hack záludného hodu.

Celý zdrojový kód funkčného rozhrania a obalové metódy si môžete stiahnuť odtiaľto a testovacie triedy odtiaľto, na Github.

Ak hľadáte hotové pracovné riešenia, stojí za to vyskúšať si projekt ThrowingFunction.


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