Bevezetés a Vavr-ba

1. Áttekintés

Ebben a cikkben meg fogjuk vizsgálni, hogy pontosan mi a Vavr, miért van szükségünk rá és hogyan kell felhasználni a projektjeinkben.

Vavr egy funkcionális könyvtár a Java 8+ számára, amely változhatatlan adattípusokat és funkcionális vezérlési struktúrákat biztosít.

1.1. Maven-függőség

A Vavr használatához hozzá kell adnia a függőséget:

 io.vavr vavr 0.9.0 

Javasoljuk, hogy mindig a legújabb verziót használja. Ezt a linket követve szerezheti be.

2. Opció

Az Option fő célja, hogy a Java típusú rendszer kihasználásával kiküszöbölje a kódunk semmilyen ellenőrzését.

választási lehetőség egy olyan objektumtároló a Vavr-ban, amelynek hasonló végcélja van, mint például az Opcionális a Java 8. Vavr-ban választási lehetőség megvalósítja Serializálható, iterálható, és gazdagabb API-val rendelkezik.

Mivel a Java bármely objektum hivatkozásának lehet egy nulla értéket, általában a semmisséget kell ellenőriznünk ha állítások használata előtt. Ezek az ellenőrzések teszik a kódot robusztusvá és stabilá:

@Test public void givenValue_whenNullCheckNeeded_thenCorrect () {Object possibleNullObj = null; if (lehetségesNullObj == null) {lehetségesNullObj = "someDefaultValue"; } assertNotNull (lehetségesNullObj); }

Ellenőrzések nélkül az alkalmazás összeomolhat egy egyszerű miatt NPE:

@Test (várható = NullPointerException.class) public void givenValue_whenNullCheckNeeded_thenCorrect2 () {Object possibleNullObj = null; assertEquals ("somevalue", lehetségesNullObj.toString ()); }

Az ellenőrzések azonban elkészítik a kódot bő és nem annyira olvasható, különösen, ha a ha az állítások többször egymásba ágyazódnak.

választási lehetőség megoldja ezt a problémát a teljes megszüntetésével nullák és minden lehetséges forgatókönyvre érvényes objektum hivatkozással helyettesítjük őket.

Val vel választási lehetőség a nulla érték értékeli a Egyik sem, míg a nem null érték a Néhány:

@Test public void givenValue_whenCreatesOption_thenCorrect () {Option noneOption = Option.of (null); Option Option = Option.of ("val"); assertEquals ("Nincs", noneOption.toString ()); assertEquals ("Some (val)", someOption.toString ()); }

Ezért az objektumértékek közvetlen használata helyett ajánlatos becsomagolni őket egy választási lehetőség példának megfelelően.

Figyelje meg, hogy nem kellett ellenőriznünk telefonálás előtt Sztring mégsem kellett foglalkoznunk a NullPointerException mint korábban tettük. Opciók Sztring minden hívás során értékes értékeket ad vissza nekünk.

A szakasz második részletében a nulla pipa, amelyben alapértelmezett értéket rendelnénk a változóhoz, mielőtt megkísérelnénk használni. választási lehetőség képes ezt egyetlen sorban kezelni, még akkor is, ha van null:

@Test public void givenNull_whenCreatesOption_thenCorrect () {String name = null; Option nameOption = Option.of (név); assertEquals ("baeldung", nameOption.getOrElse ("baeldung")); }

Vagy nem null:

@Test public void givenNonNull_whenCreatesOption_thenCorrect () {String name = "baeldung"; Option nameOption = Option.of (név); assertEquals ("baeldung", nameOption.getOrElse ("notbaeldung")); }

Figyelje meg, hogyan, anélkül nulla ellenőrzi, egy sorban kaphatunk értéket vagy visszaadhatunk egy alapértelmezett értéket.

3. Tuple

Nincs közvetlen megfelelője a Java adatszerkezetének. A kettő a funkcionális programozási nyelvekben elterjedt fogalom. A csomók változhatatlanok, és több, különböző típusú objektumot képesek típusbiztonságban elhelyezni.

A Vavr a 8-as Java-t hozza létre Tuple1, Tuple2 nak nek Tuple8 az elvenni kívánt elemek számától függően.

Jelenleg nyolc elem felső határa van. Hozzáférünk egy olyan elemhez, mint egy tuple._n hol n hasonló a tömbök indexének fogalmához:

public void whenCreatesTuple_thenCorrect1 () {Tuple2 java8 = Tuple.of ("Java", 8); String elem1 = java8._1; int elem2 = java8._2 (); assertEquals ("Java", elem1); assertEquals (8, element2); }

Figyelje meg, hogy az első elemet a n == 1. Tehát egy kettő nem használ nullpontot, mint egy tömb. Az elemek típusait, amelyeket a duplában tárolnak, a típusdeklarációban meg kell adni, a fent és az alábbiak szerint:

@Test public void whenCreatesTuple_thenCorrect2 () {Tuple3 java8 = Tuple.of ("Java", 8, 1.8); String elem1 = java8._1; int elem2 = java8._2 (); kettős elem3 = java8._3 (); assertEquals ("Java", elem1); assertEquals (8, element2); assertEquals (1,8, elem3, 0,1); }

A kettes helye bármilyen típusú objektumok rögzített csoportjának tárolása, amelyek jobban feldolgozhatók egységként és átadhatók. Ennél nyilvánvalóbb use esetben egynél több objektumot ad vissza egy függvényből vagy egy módszerből Java-ban.

4. Próbáld meg

Vavrban, Próbáld ki konténer egy számításhozami kivételt eredményezhet.

Mint választási lehetőség semmissé váló tárgyat becsomagol, hogy ne kelljen kifejezetten gondoskodnunk róla nullák val vel ha ellenőrzések, Próbáld ki becsomagol egy számítást, hogy ne kelljen kifejezetten vigyáznunk a kivételekre próbáld elkapni blokkok.

Vegyük például a következő kódot:

@Test (várható = ArithmeticException.class) public void givenBadCode_whenThrowsException_thenCorrect () {int i = 1/0; }

Nélkül próbáld elkapni blokkolja, az alkalmazás összeomlik. Ennek elkerülése érdekében az állítást be kell csomagolnia a próbáld elkapni Blokk. A Vavr segítségével ugyanazt a kódot becsomagolhatjuk a Próbáld ki és kap egy eredményt:

@Test public void givenBadCode_whenTryHandles_thenCorrect () {Try result = Try.of (() -> 1/0); assertTrue (result.isFailure ()); }

Azt, hogy a számítás sikeres volt-e vagy sem, azután tetszés szerint ellenőrizhető a kód bármely pontján.

A fenti részletben úgy döntöttünk, hogy egyszerűen ellenőrizzük a sikert vagy a kudarcot. Választhatunk egy alapértelmezett értéket is:

@Test public void givenBadCode_whenTryHandles_thenCorrect2 () {Try computation = Try.of (() -> 1/0); int errorSentinel = eredmény.getOrElse (-1); assertEquals (-1, errorSentinel); }

Vagy akár kifejezetten kivételt tenni a választásunk szerint:

@Test (várható = ArithmeticException.class) public void givenBadCode_whenTryHandles_thenCorrect3 () {Try result = Try.of (() -> 1/0); eredmény.getOrElseThrow (ArithmeticException :: új); }

A fenti esetekben a Vavr-nek köszönhetően mi irányíthatjuk a számítás után történteket Próbáld ki.

5. Funkcionális interfészek

A Java 8 érkezésével a funkcionális interfészek beépítettek és könnyebben használhatók, különösen a lambdákkal kombinálva.

A Java 8 azonban csak két alapvető funkciót biztosít. Az egyik csak egyetlen paramétert vesz fel és eredményt ad:

@Test public void givenJava8Function_whenWorks_thenCorrect () {Funkció négyzet = (num) -> num * num; int eredmény = négyzet.alkalmazás (2); assertEquals (4, eredmény); }

A második csak két paramétert vesz fel és eredményt ad:

@Test public void givenJava8BiFunction_whenWorks_thenCorrect () {BiFunction sum = (num1, num2) -> num1 + num2; int eredmény = összeg.alkalmazás (5, 7); assertEquals (12, eredmény); }

A másik oldalon a Vavr tovább terjeszti a funkcionális interfészek gondolatát a Java-ban azáltal, hogy legfeljebb nyolc paramétert támogat, és az API-t feljegyzések, kompozíciók és curry módszerekkel egészíti ki.

Csakúgy, mint a sorrendben, ezeket a funkcionális interfészeket az általuk alkalmazott paraméterek száma szerint nevezik el: Funkció0, Funkció1, Funkció2 stb. A Vavr-rel a fenti két függvényt írtuk volna így:

@Test public void givenVavrFunction_whenWorks_thenCorrect () {Funkció1 négyzet = (szám) -> szám * szám; int eredmény = négyzet.alkalmazás (2); assertEquals (4, eredmény); }

és ez:

@Test public void givenVavrBiFunction_whenWorks_thenCorrect () {Funkció2 összeg = (szám1, szám2) -> szám1 + szám2; int eredmény = összeg.alkalmazás (5, 7); assertEquals (12, eredmény); }

Ha nincs paraméter, de még mindig szükségünk van egy kimenetre, akkor a Java 8-ban az a-t kell használnunk Fogyasztó típusú, a Vavr-ban Funkció0 segít-e:

@Test public void whenCreatesFunction_thenCorrect0 () {Function0 getClazzName = () -> this.getClass (). GetName (); Karakterlánc clazzName = getClazzName.apply (); assertEquals ("com.baeldung.vavr.VavrTest", clazzName); }

Mit szólnál egy ötparaméteres függvényhez, csak használat kérdése Funkció5:

@Test public void whenCreatesFunction_thenCorrect5 () {Function5 concat = (a, b, c, d, e) -> a + b + c + d + e; String finalString = concat.apply ("Hello", "world", "!", "Learn", "Vavr"); assertEquals ("Helló világ! Tanuld meg a Vavr-t", finalString); }

Kombinálhatjuk a statikus gyári módszert is Funkció a függvények bármelyikéhez Vavr függvény létrehozása a módszer referenciájából. Mint ha a következők lennének összeg módszer:

public int összeg (int a, int b) {return a + b; }

Így létrehozhatunk belőle egy függvényt:

@Test public void whenCreatesFunctionFromMethodRef_thenCorrect () {Function2 sum = Function2.of (this :: sum); int összegezve = összeg.alkalmazás (5, 6); assertEquals (11, összegezve); }

6. Gyűjtemények

A Vavr csapata nagy erőfeszítéseket tett egy új gyűjteményes API megtervezése érdekében, amely megfelel a funkcionális programozás, azaz a kitartás, a változtathatóság követelményeinek.

A Java gyűjtemények változtathatóak, így a programok hibájának nagy forrása, különösen a párhuzamosság jelenlétében. A Gyűjtemény Az interfész olyan módszereket kínál, mint ez:

interfész Gyűjtemény {void clear (); }

Ez a módszer eltávolítja a gyűjtemény összes elemét (mellékhatást produkál), és nem ad vissza semmit. Olyan osztályok, mint ConcurrentHashMap a már létrehozott problémák kezelésére jöttek létre.

Egy ilyen osztály nemcsak nulla marginális haszonnal jár, hanem rontja annak az osztálynak a teljesítményét is, amelynek kiskapuit megpróbál kitölteni.

A változtathatatlansággal ingyen kapjuk meg a menetbiztonságot: nem kell új osztályokat írni olyan problémák kezelésére, amelyeknek eleve nem kellene ott lenniük.

A Java-gyűjtemények megváltoztathatatlanságát növelő más létező taktikák még mindig több problémát okoznak, nevezetesen a kivételek:

@Test (várható = UnsupportedOperationException.class) public void whenImmutableCollectionThrows_thenCorrect () {java.util.List wordList = Arrays.asList ("abracadabra"); java.util.List list = Gyűjtemények.unmodifiableList (wordList); list.add ("bumm"); }

Az összes fenti probléma nem létezik a Vavr gyűjteményekben.

Lista létrehozása a Vavr alkalmazásban:

@Test public void whenCreatesVavrList_thenCorrect () {List intList = List.of (1, 2, 3); assertEquals (3, intList.length ()); assertEquals (új egész szám (1), intList.get (0)); assertEquals (új egész szám (2), intList.get (1)); assertEquals (új egész szám (3), intList.get (2)); }

Az API-k a rendelkezésre álló listán történő számítások elvégzésére is rendelkezésre állnak:

@Test public void whenSumsVavrList_thenCorrect () {int sum = List.of (1, 2, 3) .sum (). IntValue (); assertEquals (6, összeg); }

A Vavr gyűjtemények kínálják a Java Gyűjtemények keretrendszerében található közös osztályok nagy részét, és valójában az összes funkció megvalósul.

Az elvihető az állandóság, az érvénytelen visszatérési típusok eltávolítása és mellékhatásokat előállító API-k, gazdagabb készlet funkciókat működtetni az alapul szolgáló elemeken, nagyon rövid, robusztus és kompakt kód összehasonlítva a Java gyűjtési műveleteivel.

A Vavr gyűjtemények teljes lefedettsége meghaladja a cikk kereteit.

7. Érvényesítés

Vavr hozza a koncepciót Alkalmazható Functor a Java-ra a funkcionális programozási világból. A legegyszerűbb kifejezéssel an Alkalmazható Functor lehetővé teszi számunkra, hogy műveletek sorozatát hajtsuk végre az eredmények felhalmozása közben.

Osztály vavr.control. Érvényesítés megkönnyíti a hibák felhalmozását. Ne feledje, hogy általában egy program azonnal leáll, amikor hiba lép fel.

Azonban, Érvényesítés folytatja a hibák feldolgozását és felhalmozását, hogy a program kötegként működjön rajtuk.

Vegye figyelembe, hogy felhasználókat regisztrálunk név és kor és először minden bemenetet meg akarunk venni, és el kell dönteni, hogy létrehozunk-e egy Személy példányt, vagy adja vissza a hibák listáját. Itt van a miénk Személy osztály:

public class Személy {private String név; privát int kor; // szabványos kivitelezők, beállítók és szerelők, toString}

Ezután létrehozunk egy nevű osztályt PersonValidator. Minden mezőt érvényesíteni fog egy módszerrel, és egy másik módszerrel az összes eredményt egybe lehet egyesíteni Érvényesítés példa:

class PersonValidator {String NAME_ERR = "Érvénytelen karakterek a névben:"; String AGE_ERR = "Az életkornak legalább 0-nak kell lennie"; nyilvános érvényesítés validatePerson (karakterlánc neve, int kor) {return Validation.combine (validateName (név), validateAge (életkor)). ap (Person :: new); } private Validation validateName (karakterlánc neve) {String invalidChars = név.replaceAll ("[a-zA-Z]", ""); return invalidChars.isEmpty ()? Validation.valid (név): Validation.invalid (NAME_ERR + érvénytelenChars); } private Validation validateAge (int age) {visszatérési kor <0? Validation.invalid (AGE_ERR): Validation.valid (kor); }}

A szabály a kor az, hogy 0-nál nagyobb egész szám legyen, és a név az, hogy nem tartalmazhat speciális karaktereket:

@Test public void whenValidationWorks_thenCorrect () {PersonValidator personValidator = new PersonValidator (); Érvényesítés valid = personValidator.validatePerson ("John Doe", 30); Érvényesítés érvénytelen = personValidator.validatePerson ("John? Doe! 4", -1); assertEquals ("Érvényes (Személy [név = John Doe, életkor = 30]]", érvényes.toString ()); assertEquals ("Érvénytelen (Lista (Érvénytelen karakterek a névben:?! 4, Az életkornak legalább 0-nak kell lennie))", érvénytelen.toString ()); }

Érvényes értéket tartalmaz a Érvényesítés. Érvényes Például az érvényesítési hibák listáját az a tartalmazza Érvényesítés. Érvénytelen példa. Tehát minden érvényesítési módszernek vissza kell adnia a kettő egyikét.

Belül Érvényesítés. Érvényes példánya Személy míg bent van Érvényesítés. Érvénytelen a hibák listája.

8. Lusta

Lusta olyan konténer, amely lustán kiszámított értéket képvisel, vagyis a számítást addig halasztják, amíg az eredményre szükség van. Ezenkívül az értékelt értéket gyorsítótárba helyezzük vagy memóriába helyezzük, és minden alkalommal újra és újra visszaadjuk, amikor arra szükség van, a számítás megismétlése nélkül:

@Test public void givenFunction_whenEvaluatesWithLazy_thenCorrect () {Lusta lusta = Lusta.of (Math :: random); assertFalse (lusta.isEvaluated ()); kettős val1 = lusta.get (); assertTrue (lusta.ÉRTÉKELT ()); kettős val2 = lusta.get (); assertEquals (val1, val2, 0,1); }

A fenti példában az a funkció, amelyet értékelünk Math.random. Figyeljük meg, hogy a második sorban ellenőrizzük az értéket, és rájövünk, hogy a függvény még nincs végrehajtva. Ennek oka, hogy továbbra sem mutatunk érdeklődést a visszatérési érték iránt.

A harmadik kódsorban hívással felhívjuk a figyelmet a számítási értékre Lusta.get. Ezen a ponton a függvény végrehajtja és Lusta.értékelődött true-val tér vissza.

Tovább megyünk, és megerősítjük a memoization bitet Lusta azzal, hogy megkísérli kap megint az érték. Ha az általunk biztosított funkciót újra végrehajtanák, akkor biztosan kapunk egy másik véletlenszerű számot.

Azonban, Lusta ismét lustán adja vissza az eredetileg kiszámított értéket, amint azt a végső állítás megerősíti.

9. Mintaillesztés

A mintaillesztés szinte minden funkcionális programozási nyelvben natív fogalom. A Java-ban egyelőre nincs ilyen.

Ehelyett, amikor számítást akarunk végrehajtani, vagy a kapott input alapján visszaadunk egy értéket, többször használunk ha utasítások a végrehajtáshoz szükséges kód feloldásához:

@Test public void whenIfWorksAsMatcher_thenCorrect () {int input = 3; Karakterlánc kimenet; if (input == 0) {output = "nulla"; } if (input == 1) {output = "egy"; } if (input == 2) {output = "kettő"; } if (input == 3) {output = "három"; } else {output = "ismeretlen"; } assertEquals ("három", kimenet); }

Hirtelen láthatjuk a kódot, amely több sort átível, miközben csak három esetet ellenőriz. Minden ellenőrzés három sornyi kódot vesz fel. Mi lenne, ha száz esetet kellene ellenőriznünk, ezek körülbelül 300 sorosak lennének, nem szép!

Egy másik alternatíva az a használata kapcsoló nyilatkozat:

@Test public void whenSwitchWorksAsMatcher_thenCorrect () {int input = 2; Karakterlánc kimenet; kapcsoló (bemenet) {eset 0: kimenet = "nulla"; szünet; 1. eset: kimenet = "egy"; szünet; 2. eset: kimenet = "kettő"; szünet; 3. eset: kimenet = "három"; szünet; alapértelmezett: output = "ismeretlen"; szünet; } assertEquals ("kettő", kimenet); }

Nem jobb. Még mindig átlagosan 3 sort adunk csekkenként. Sok zavar és potenciális hiba. Elfelejtve a szünet záradék nem kérdés a fordítás idején, de később nehezen észlelhető hibákat eredményezhet.

A Vavr-ban az egészet kicseréljük kapcsoló blokkolja a mérkőzés módszer. Minden egyes ügy vagy ha állítás helyébe a Ügy módszer meghívása.

Végül az atomminták, mint a $() cserélje ki azt a feltételt, amely kiértékeli egy kifejezést vagy értéket. Ezt a második paraméterként is megadjuk Ügy:

@Test public void whenMatchworks_thenCorrect () {int input = 2; Karakterlánc kimenet = egyezés (bemenet) .of (Case ($ (1), "one"), Case ($ (2), "two"), Case ($ (3), "three"), Case ($ ( ), "?")); assertEquals ("kettő", kimenet); }

Figyelje meg, mennyire tömör a kód, csekkenként átlagosan csak egy sort írva. A mintaillesztési API ennél sokkal hatékonyabb, és bonyolultabb dolgokra képes.

Például az atomi kifejezéseket predikátummal helyettesíthetjük. Képzelje el, hogy egy konzol parancsot elemzünk Segítség és változat zászlók:

Match (arg) .of (Case ($ (isIn ("- h", "--help")), o -> run (this :: displayHelp)), Case ($ (isIn ("- v", " --version ")), o -> run (this :: displayVersion)), Case ($ (), o -> run (() -> {dobja be az új IllegalArgumentException (arg);})));

Egyes felhasználók jobban ismerhetik a gyorsírás (-v), míg mások a teljes verziót (–version). A jó tervezőnek figyelembe kell vennie ezeket az eseteket.

Anélkül, hogy többre lenne szükség ha állítások, több feltételről is gondoskodtunk.A predikátumokról, a többféle körülményről és a mintaillesztés mellékhatásairól többet megtudhatunk külön cikkben.

10. Következtetés

Ebben a cikkben bemutattuk a Vavr-t, a Java 8 népszerű funkcionális programozási könyvtárát. Megvitattuk azokat a főbb szolgáltatásokat, amelyeket gyorsan adaptálhatunk a kódunk fejlesztéséhez.

A cikk teljes forráskódja a Github projektben érhető el.