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.