Kivételek a Java 8 Lambda kifejezésekben

1. Áttekintés

A Java 8-ban a Lambda Expressions megkönnyítette a funkcionális programozást azáltal, hogy tömör módon kifejezte a viselkedést. Azonban a Funkcionális interfészek a JDK által nyújtott információk nem nagyon foglalkoznak a kivételekkel - és a kód bőbeszédűvé és nehézkessé válik a kezelésük során.

Ebben a cikkben a lambda kifejezések írásakor a kivételek kezelésének néhány módját tárjuk fel.

2. Ellenőrizetlen kivételek kezelése

Először értsük meg a problémát egy példával.

Nekünk van Lista és egy konstansot, mondjuk 50-et akarunk osztani a lista minden elemével, és kinyomtatjuk az eredményeket:

Egész számok = tömbök. AsList (3, 9, 7, 6, 10, 20); integers.forEach (i -> System.out.println (50 / i));

Ez a kifejezés működik, de van egy probléma. Ha a lista egyik eleme az 0, akkor kapunk egy Aritmetikai kivétel: / nullával. Javítsuk ki ezt egy hagyományos használatával próbáld elkapni blokkolja, hogy minden ilyen kivételt naplózunk, és folytatjuk a következő elemek végrehajtását:

Egész számok = tömbök. AsList (3, 9, 7, 0, 10, 20); integers.forEach (i -> {próbáld meg {System.out.println (50 / i);} catch (ArithmeticException e) {System.err.println ("Aritmetikai kivétel történt:" + e.getMessage ());}} );

A ... haszna próbáld elkapni megoldja a problémát, de az a tömörsége Lambda kifejezés elveszett, és ez már nem egy kis funkció, mint kellene.

A probléma kezeléséhez írhatunk lambda burkoló a lambda funkcióhoz. Nézzük meg a kódot, hogy hogyan működik:

statikus fogyasztói lambdaWrapper (fogyasztói fogyasztó) {return i -> {try {consumer.accept (i); } catch (ArithmeticException e) {System.err.println ("Aritmetikai kivétel történt:" + e.getMessage ()); }}; }
Egész számok = tömbök. AsList (3, 9, 7, 0, 10, 20); integers.forEach (lambdaWrapper (i -> System.out.println (50 / i)));

Eleinte írtunk egy burkoló módszert, amely felelős lesz a kivétel kezeléséért, majd paraméterként továbbítottuk a lambda kifejezést ehhez a módszerhez.

A burkoló módszer a várakozásoknak megfelelően működik, de azt állíthatja, hogy alapvetően eltávolítja a próbáld elkapni blokkolja a lambda kifejezést és áthelyezi egy másik módszerre, és ez nem csökkenti az írandó kódsorok tényleges számát.

Ez igaz ebben az esetben, amikor a burkoló egy adott felhasználási esetre jellemző, de általános módszereket használhatunk a módszer javítására, és számos más esetben alkalmazhatjuk:

statikus Consumer consumerWrapper (Consumer consumer, Class clazz) {return i -> {try {consumer.accept (i); } catch (Exception ex) {try {E exCast = clazz.cast (ex); System.err.println ("Kivétel történt:" + exCast.getMessage ()); } fogás (ClassCastException ccEx) {dobás ex; }}}; }
Egész számok = tömbök. AsList (3, 9, 7, 0, 10, 20); integers.forEach (consumerWrapper (i -> System.out.println (50 / i), ArithmeticException.class));

Mint láthatjuk, a wrapper módszerünknek ez az iterációja két argumentumot igényel, a lambda kifejezést és a típusát Kivétel hogy elkapják. Ez a lambda csomagoló minden adattípust képes kezelni, nemcsak Egész számok, és elkapni bármilyen típusú kivételt, és nem a szuperosztályt Kivétel.

Figyelje meg azt is, hogy a metódus nevét megváltoztattuk lambdaWrapper nak nek consumerWrapper. Ez azért van, mert ez a módszer csak a lambda kifejezéseket kezeli Funkcionális interfész típusú Fogyasztó. Hasonló burkoló módszereket írhatunk más funkcionális interfészekhez is Funkció, BiFunction, BiConsumer stb.

3. Az ellenőrzött kivételek kezelése

Módosítsuk az előző szakasz példáját, és a konzolra történő nyomtatás helyett írjunk egy fájlba.

static void writeToFile (Integer integer) dobja az IOException {// logikát, hogy írjon fájlba, amely az IOExceptiont dobja}

Ne feledje, hogy a fenti módszer dobhatja a IOException.

Egész számok = tömbök. AsList (3, 9, 7, 0, 10, 20); integers.forEach (i -> writeToFile (i));

Összeállításkor kapjuk meg a hibát:

java.lang.Error: Megoldatlan fordítási probléma: Kezeletlen kivétel típusú IOException

Mivel IOException ellenőrzött kivétel, ezt kifejezetten kezelnünk kell. Két lehetőségünk van.

Először is, egyszerűen kizárhatjuk a kivételt a módszerünkből, és máshol is elintézhetjük.

Alternatív megoldásként kezelhetjük a lambda kifejezést használó módszeren belül is.

Fedezzük fel mindkét lehetőséget.

3.1. Ellenőrzött kivétel dobása a lambda kifejezésekből

Lássuk, mi történik, amikor kijelentjük a IOException a fő- módszer:

public static void main (String [] args) dobja az IOException {List egész számokat = Arrays.asList (3, 9, 7, 0, 10, 20); integers.forEach (i -> writeToFile (i)); }

Még mindig, ugyanazt a kezeletlen hibát kapjuk IOException az összeállítás során.

java.lang.Error: Megoldatlan fordítási probléma: Kezeletlen kivétel típusú IOException

Ez azért van, mert a lambda kifejezések hasonlóak az Anonymous Inner Classes-hoz.

A mi esetünkben, writeToFile módszer a Fogyasztó funkcionális interfész.

Vessünk egy pillantást a FogyasztóDefiníciója:

@FunctionalInterface nyilvános felület Fogyasztó {void accept (T t); }

Ahogy látjuk elfogad A metódus nem jelent bejelölt kivételt. Ez az oka writeToFile nem szabad dobni a IOException.

A legegyszerűbb módszer az a használata lenne próbáld elkapni blokkolja, csomagolja be a bejelölt kivételt egy be nem ellenőrzött kivételbe, és dobja újra:

Egész számok = tömbök. AsList (3, 9, 7, 0, 10, 20); integers.forEach (i -> {próbáld meg {writeToFile (i);} catch (IOException e) {dobj új RuntimeException (e);}}); 

Ez megkapja a kódot lefordításra és futtatásra. Ez a megközelítés azonban ugyanazt a kérdést vezeti be, amelyet az előző szakaszban már tárgyaltunk - bőbeszédű és nehézkes.

Jobbak lehetünk ennél.

Hozzunk létre egy egyedi funkcionális felületet egyetlen elfogad módszer, amely kivételt vet.

@FunctionalInterface nyilvános felület ThrowingConsumer {void accept (T t) dob E; }

Most pedig alkalmazzunk egy olyan burkoló módszert, amely képes visszavetni a kivételt:

statikus Consumer throwingConsumerWrapper (ThrowingConsumer throwingConsumer) {return i -> {try {throwingConsumer.accept (i); } catch (Exception ex) {dobjon új RuntimeException (ex); }}; }

Végül le tudjuk egyszerűsíteni a használat módját writeToFile módszer:

Egész számok = tömbök. AsList (3, 9, 7, 0, 10, 20); integers.forEach (throwingConsumerWrapper (i -> writeToFile (i)));

Ez még mindig egyfajta megoldás, de a végeredmény elég tiszta és egyértelműen könnyebben fenntartható.

Mindkettő, a DobásFogyasztó és a throwingConsumerWrapper általánosak és könnyen felhasználhatók alkalmazásunk különböző helyein.

3.2. Ellenőrzött kivétel kezelése a lambda kifejezésben

Ebben az utolsó részben módosítjuk a burkolót, hogy kezelje az ellenőrzött kivételeket.

Mivel a mi DobásFogyasztó Az interfész generikákat használ, könnyen kezelhetünk minden konkrét kivételt.

statikus FogyasztókezelésConsumerWrapper (ThrowingConsumer throwingConsumer, osztály kivételes osztály) {return i -> {try {throwingConsumer.accept (i); } catch (Exception ex) {try {E exCast = kivételClass.cast (ex); System.err.println ("Kivétel történt:" + exCast.getMessage ()); } catch (ClassCastException ccEx) {dobjon új RuntimeException (ex); }}}; }

Nézzük meg, hogyan kell használni a gyakorlatban:

Egész számok = tömbök. AsList (3, 9, 7, 0, 10, 20); integers.forEach (handlingConsumerWrapper (i -> writeToFile (i), IOException.class));

Vegye figyelembe, hogy a fenti kód csak fogantyúk IOException, mivel minden másfajta kivételt a RuntimeException .

4. Következtetés

Ebben a cikkben bemutattuk, hogyan kezelhetünk egy konkrét kivételt lambda expresszióban anélkül, hogy a tömörséget elvesztenénk a burkoló módszerek segítségével. Megtanultuk azt is, hogyan lehet dobási alternatívákat írni a JDK-ban jelen lévő funkcionális interfészekhez, hogy dobjanak vagy kezeljenek egy ellenőrzött kivételt.

Egy másik módszer az alattomos dobások feltörésének feltárása lenne.

A Functional Interface és a wrapper módszerek teljes forráskódja innen tölthető le, a teszt osztályok pedig innen, a Githubról.

Ha a dobozon kívüli megoldásokat keresi, akkor érdemes megnézni a ThrowingFunction projektet.