Introduktion til Vavr

1. Oversigt

I denne artikel vil vi undersøge nøjagtigt, hvad Vavr er, hvorfor vi har brug for det, og hvordan vi bruger det i vores projekter.

Vavr er en funktionelt bibliotek til Java 8+, der giver uforanderlige datatyper og funktionelle kontrolstrukturer.

1.1. Maven afhængighed

For at bruge Vavr skal du tilføje afhængighed:

 io.vavr vavr 0.9.0 

Det anbefales altid at bruge den nyeste version. Du kan få det ved at følge dette link.

2. Valgmulighed

Hovedmålet med Option er at eliminere nulchecks i vores kode ved at udnytte Java-typesystemet.

Mulighed er en objektbeholder i Vavr med et lignende slutmål som Valgfrit i Java 8. Vavr's Mulighed redskaber Serialiserbar, Iterabel, og har en rigere API.

Da enhver objektreference i Java kan have en nul værdi, skal vi normalt kontrollere for ugyldighed med hvis udsagn inden brug. Disse kontroller gør koden robust og stabil:

@Test offentlig ugyldighed givenValue_whenNullCheckNeeded_thenCorrect () {Objekt muligtNullObj = null; if (possibleNullObj == null) {possibleNullObj = "someDefaultValue"; } assertNotNull (possibleNullObj); }

Uden kontrol kan applikationen gå ned på grund af en simpel NPE:

@Test (forventet = NullPointerException.class) offentlig ugyldighed givenValue_whenNullCheckNeeded_thenCorrect2 () {Objekt muligtNullObj = null; assertEquals ("somevalue", possibleNullObj.toString ()); }

Imidlertid udgør kontrollen koden detaljeret og ikke så læsbar, især når hvis udsagn ender med at blive indlejret flere gange.

Mulighed løser dette problem ved helt at eliminere nul og erstatte dem med en gyldig objektreference for hvert muligt scenario.

Med Mulighed -en nul værdi evalueres til en forekomst af Ingen, mens en ikke-nul-værdi evalueres til en forekomst af Nogle:

@ Test offentligt ugyldigt givenValue_whenCreatesOption_thenCorrect () {Option noneOption = Option.of (null); Option someOption = Option.of ("val"); assertEquals ("Ingen", noneOption.toString ()); assertEquals ("Some (val)", someOption.toString ()); }

Derfor er det tilrådeligt at pakke dem ind i en i stedet for at bruge objektværdier direkte Mulighed eksempel som vist ovenfor.

Bemærk, at vi ikke behøvede at foretage en check, før vi ringede toString alligevel havde vi ikke at gøre med en NullPointerException som vi havde gjort før. Muligheder toString returnerer os meningsfulde værdier i hvert opkald.

I det andet uddrag af dette afsnit havde vi brug for en nul check, hvor vi tildeler variablen en standardværdi, inden vi prøver at bruge den. Mulighed kan håndtere dette i en enkelt linje, selvom der er en null:

@Test offentlig ugyldighed givenNull_whenCreatesOption_thenCorrect () {String name = null; Option nameOption = Option.of (navn); assertEquals ("baeldung", nameOption.getOrElse ("baeldung")); }

Eller en ikke-nul:

@Test offentlig ugyldighed givenNonNull_whenCreatesOption_thenCorrect () {String name = "baeldung"; Option nameOption = Option.of (navn); assertEquals ("baeldung", nameOption.getOrElse ("notbaeldung")); }

Bemærk hvordan, uden nul kontrollerer, kan vi få en værdi eller returnere en standard i en enkelt linje.

3. Tuple

Der er ingen direkte ækvivalent med en tupledatastruktur i Java. En tuple er et almindeligt koncept i funktionelle programmeringssprog. Tuples er uforanderlige og kan indeholde flere objekter af forskellige typer på en typesikker måde.

Vavr bringer tupler til Java 8. Tupler er af typen Tuple1, Tuple2 til Tuple8 afhængigt af antallet af elementer, de skal tage.

Der er i øjeblikket en øvre grænse på otte elementer. Vi får adgang til elementer af en tuple som tuple._n hvor n svarer til forestillingen om et indeks i arrays:

offentlig ugyldig nårCreatesTuple_thenCorrect1 () {Tuple2 java8 = Tuple.of ("Java", 8); Strengelement1 = java8._1; int element2 = java8._2 (); assertEquals ("Java", element1); assertEquals (8, element2); }

Bemærk, at det første element hentes med n == 1. Så en tuple bruger ikke en nul base som en matrix. De typer af elementer, der vil blive gemt i tuplen, skal deklareres i dens typedeklaration som vist ovenfor og nedenfor:

@Test offentlig ugyldig nårCreatesTuple_thenCorrect2 () {Tuple3 java8 = Tuple.of ("Java", 8, 1.8); Strengelement1 = java8._1; int element2 = java8._2 (); dobbelt element3 = java8._3 (); assertEquals ("Java", element1); assertEquals (8, element2); assertEquals (1,8, element3, 0,1); }

En tuples sted er at opbevare en fast gruppe af objekter af enhver art, der bedre behandles som en enhed og kan sendes rundt. En mere indlysende use case returnerer mere end et objekt fra en funktion eller en metode i Java.

4. Prøv

I Vavr, Prøve er en beholder til beregninghvilket kan resultere i en undtagelse.

Som Mulighed indpakker et ugyldigt objekt, så vi ikke udtrykkeligt behøver at tage os af det nul med hvis kontrol, Prøve indpakker en beregning, så vi ikke udtrykkeligt behøver at tage os af undtagelser med prøve-fangst blokke.

Tag f.eks. Følgende kode:

@Test (forventet = ArithmeticException.class) offentlig ugyldighed givenBadCode_whenThrowsException_thenCorrect () {int i = 1/0; }

Uden prøve-fangst blokerer, ville applikationen gå ned. For at undgå dette skal du pakke udsagnet i en prøve-fangst blok. Med Vavr kan vi pakke den samme kode i en Prøve eksempel og få et resultat:

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

Om beregningen var vellykket eller ej, kan derefter inspiceres ved valg på ethvert tidspunkt i koden.

I ovenstående uddrag har vi valgt blot at kontrollere for succes eller fiasko. Vi kan også vælge at returnere en standardværdi:

@Test offentligt ugyldigt givenBadCode_whenTryHandles_thenCorrect2 () {Try computation = Try.of (() -> 1/0); int errorSentinel = result.getOrElse (-1); assertEquals (-1, errorSentinel); }

Eller endda eksplicit at smide en undtagelse fra vores valg:

@Test (forventet = ArithmeticException.class) offentlig ugyldighed givenBadCode_whenTryHandles_thenCorrect3 () {Try result = Try.of (() -> 1/0); result.getOrElseThrow (ArithmeticException :: new); }

I alle ovenstående tilfælde har vi kontrol over, hvad der sker efter beregningen, takket være Vavrs Prøve.

5. Funktionelle grænseflader

Med ankomsten af ​​Java 8 er funktionelle grænseflader indbygget og lettere at bruge, især når de kombineres med lambdas.

Java 8 indeholder dog kun to grundlæggende funktioner. Man tager kun en enkelt parameter og producerer et resultat:

@Test offentligt ugyldigt givetJava8Function_whenWorks_thenCorrect () {Funktion kvadrat = (num) -> num * num; int-resultat = kvadratisk. anvend (2); assertEquals (4, resultat); }

Den anden tager kun to parametre og giver et resultat:

@Test offentlig tomrum givetJava8BiFunction_whenWorks_thenCorrect () {BiFunction sum = (num1, num2) -> num1 + num2; int-resultat = sum.apply (5, 7); assertEquals (12, resultat); }

På bagsiden udvider Vavr ideen om funktionelle grænseflader i Java yderligere ved at understøtte op til maksimalt otte parametre og krydre API'et med metoder til memoization, komposition og currying.

Ligesom tupler navngives disse funktionelle grænseflader efter antallet af parametre, de tager: Funktion0, Funktion 1, Funktion2 osv. Med Vavr ville vi have skrevet ovenstående to funktioner som denne:

@Test offentligt ugyldigt givetVavrFunction_whenWorks_thenCorrect () {Funktion1 firkant = (num) -> num * num; int resultat = kvadratisk. anvend (2); assertEquals (4, resultat); }

og dette:

@Test offentligt ugyldigt givetVavrBiFunction_whenWorks_thenCorrect () {Funktion2 sum = (num1, num2) -> num1 + num2; int-resultat = sum.apply (5, 7); assertEquals (12, resultat); }

Når der ikke er nogen parameter, men vi stadig har brug for et output, skal vi i Java 8 bruge en Forbruger type, i Vavr Funktion0 er der for at hjælpe:

@Test offentlig ugyldig nårCreatesFunction_thenCorrect0 () {Function0 getClazzName = () -> this.getClass (). GetName (); String clazzName = getClazzName.apply (); assertEquals ("com.baeldung.vavr.VavrTest", clazzName); }

Hvad med en funktion med fem parametre, det er bare et spørgsmål om at bruge Funktion 5:

@Test offentligt ugyldigt nårCreatesFunction_thenCorrect5 () {Function5 concat = (a, b, c, d, e) -> a + b + c + d + e; String finalString = concat.apply ("Hej", "verden", "!", "Lær", "Vavr"); assertEquals ("Hej verden! Lær Vavr", finalString); }

Vi kan også kombinere den statiske fabriksmetode Funktion Nr. Af for en af ​​funktionerne at oprette en Vavr-funktion ud fra en metodereference. Ligesom hvis vi har følgende sum metode:

offentlig int sum (int a, int b) {return a + b; }

Vi kan oprette en funktion ud af det således:

@Test offentlig ugyldigt nårCreatesFunctionFromMethodRef_thenCorrect () {Function2 sum = Function2.of (this :: sum); int summeret = sum.apply (5, 6); assertEquals (11, opsummeret); }

6. Samlinger

Vavr-teamet har gjort en stor indsats for at designe en ny samling API, der opfylder kravene til funktionel programmering, dvs. vedholdenhed, uforanderlighed.

Java-samlinger kan ændres, hvilket gør dem til en stor kilde til programfejl, især i nærværelse af samtidighed. Det Kollektion interface giver metoder som denne:

grænsefladesamling {ugyldig klar (); }

Denne metode fjerner alle elementer i en samling (producerer en bivirkning) og returnerer intet. Klasser som ConcurrentHashMap blev oprettet for at håndtere de allerede oprettede problemer.

En sådan klasse tilføjer ikke kun nul marginale fordele, men forringer også præstationen for den klasse, hvis smuthuller den forsøger at udfylde.

Med uforanderlighed får vi trådsikkerhed gratis: ingen grund til at skrive nye klasser for at håndtere et problem, der ikke skulle være der i første omgang.

Andre eksisterende taktikker for at tilføje uforanderlighed til samlinger i Java skaber stadig flere problemer, nemlig undtagelser:

@Test (forventet = Ikke understøttetOperationException.class) offentlig ugyldig nårImmutableCollectionThrows_thenCorrect () {java.util.List wordList = Arrays.asList ("abracadabra"); java.util.List list = Collections.unmodifiableList (wordList); list.add ("boom"); }

Alle ovenstående problemer findes ikke i Vavr-samlinger.

Sådan oprettes en liste i Vavr:

@Test offentlig ugyldig nårCreatesVavrList_thenCorrect () {List intList = List.of (1, 2, 3); assertEquals (3, intList.length ()); assertEquals (nyt heltal (1), intList.get (0)); assertEquals (nyt heltal (2), intList.get (1)); assertEquals (nyt heltal (3), intList.get (2)); }

API'er er også tilgængelige til at udføre beregninger på listen på plads:

@Test offentlig ugyldig nårSumsVavrList_thenCorrect () {int sum = List.of (1, 2, 3) .sum (). IntValue (); assertEquals (6, sum); }

Vavr-samlinger tilbyder de fleste af de almindelige klasser, der findes i Java Collections Framework, og faktisk er alle funktioner implementeret.

Takeaway er uforanderlighed, fjernelse af ugyldige returtyper og API'er, der producerer bivirkninger, et rigere sæt af funktioner til at fungere på de underliggende elementer, meget kort, robust og kompakt kode sammenlignet med Java's indsamlingsoperationer.

En fuld dækning af Vavr-samlinger ligger uden for denne artikels anvendelsesområde.

7. Validering

Vavr bringer begrebet Anvendelig Functor til Java fra den funktionelle programmeringsverden. I de enkleste vendinger en Anvendelig Functor gør det muligt for os at udføre en række handlinger, mens vi akkumulerer resultaterne.

Klassen vavr.control.Validation letter ophobning af fejl. Husk, at et program normalt afsluttes, så snart der opstår en fejl.

Imidlertid, Validering fortsætter med at behandle og akkumulere fejlene for, at programmet kan fungere som en batch.

Overvej at vi registrerer brugere efter navn og alder og vi vil tage alt input først og beslutte, om vi vil oprette et Person forekomst eller returnere en liste over fejl. Her er vores Person klasse:

offentlig klasse person {privat strengnavn; privat int alder // standard konstruktører, settere og getters, toString}

Dernæst opretter vi en klasse kaldet PersonValidator. Hvert felt valideres efter en metode, og en anden metode kan bruges til at kombinere alle resultaterne til en Validering eksempel:

class PersonValidator {String NAME_ERR = "Ugyldige tegn i navn:"; String AGE_ERR = "Alder skal være mindst 0"; offentlig validering validatePerson (String name, int age) {return Validation.combine (validateName (name), validateAge (age)). ap (Person :: new); } privat validering validateName (strengnavn) {streng invalidChars = name.replaceAll ("[a-zA-Z]", ""); returnere invalidChars.isEmpty ()? Validation.valid (name): Validation.invalid (NAME_ERR + invalidChars); } privat validering validateAge (int age) {return age <0? Validation.invalid (AGE_ERR): Validation.valid (age); }}

Reglen for alder er, at det skal være et heltal større end 0 og reglen for navn er, at den ikke skal indeholde specialtegn:

@Test offentlig ugyldig nårValidationWorks_thenCorrect () {PersonValidator personValidator = ny PersonValidator (); Validering valid = personValidator.validatePerson ("John Doe", 30); Validering ugyldig = personValidator.validatePerson ("John? Doe! 4", -1); assertEquals ("Gyldig (Person [navn = John Doe, alder = 30])", valid.toString ()); assertEquals ("Ugyldig (Liste (Ugyldige tegn i navn:?! 4, Alder skal være mindst 0))", ugyldig.tilString ()); }

En gyldig værdi er indeholdt i a Validering. Gyldig For eksempel er der en liste over valideringsfejl i en Validering. Ugyldig eksempel. Så enhver valideringsmetode skal returnere en af ​​de to.

Inde Validering. Gyldig er en forekomst af Person mens du er inde Validering. Ugyldig er en liste over fejl.

8. doven

Doven er en beholder, der repræsenterer en værdi, der beregnes doven, dvs. beregning udsættes, indtil resultatet er påkrævet. Desuden caches eller gemmes den evaluerede værdi og returneres igen og igen hver gang det er nødvendigt uden at gentage beregningen:

@Test offentligt ugyldigt givenFunction_whenEvaluatesWithLazy_thenCorrect () {Lazy doven = Lazy.of (Math :: tilfældig); assertFalse (lazy.isEvaluated ()); dobbelt val1 = doven.get (); assertTrue (lazy.isEvaluated ()); dobbelt val2 = doven.get (); assertEquals (val1, val2, 0.1); }

I ovenstående eksempel er den funktion, vi evaluerer Matematik. Tilfældig. Bemærk, at vi i anden linje kontrollerer værdien og indser, at funktionen endnu ikke er udført. Dette skyldes, at vi stadig ikke har vist interesse for returværdien.

I den tredje kodelinje viser vi interesse for beregningsværdien ved at ringe Lazy.get. På dette tidspunkt udfører funktionen og Lazy. Vurderet returnerer sandt.

Vi går også videre og bekræfter memo-biten af Doven ved at forsøge at værdien igen. Hvis den funktion, vi leverede, blev udført igen, ville vi helt sikkert modtage et andet tilfældigt tal.

Imidlertid, Doven returnerer doven igen den oprindeligt beregnede værdi, som den endelige påstand bekræfter.

9. Mønstertilpasning

Mønstermatchning er et oprindeligt koncept på næsten alle funktionelle programmeringssprog. Der er ikke sådan noget i Java indtil videre.

I stedet for, når vi vil udføre en beregning eller returnere en værdi baseret på det input, vi modtager, bruger vi flere hvis erklæringer for at løse den rigtige kode, der skal udføres:

@Test offentlig ugyldig nårIfWorksAsMatcher_thenCorrect () {int input = 3; String output; hvis (input == 0) {output = "nul"; } hvis (input == 1) {output = "one"; } hvis (input == 2) {output = "to"; } hvis (input == 3) {output = "tre"; } andet {output = "ukendt"; } assertEquals ("tre", output); }

Vi kan pludselig se koden, der spænder over flere linjer, mens vi bare kontrollerer tre sager. Hver check tager tre linjer kode. Hvad hvis vi skulle kontrollere op til hundrede sager, det ville være omkring 300 linjer, ikke pænt!

Et andet alternativ er at bruge en kontakt udmelding:

@Test offentligt ugyldigt nårSwitchWorksAsMatcher_thenCorrect () {int input = 2; String output; switch (input) {case 0: output = "zero"; pause; sag 1: output = "one"; pause; tilfælde 2: output = "to"; pause; tilfælde 3: output = "tre"; pause; standard: output = "ukendt"; pause; } assertEquals ("to", output); }

Ikke bedre. Vi har stadig et gennemsnit på 3 linjer pr. Check. En masse forvirring og potentiale for bugs. Glemmer en pause klausul er ikke et problem på kompileringstidspunktet, men kan resultere i svær at opdage fejl senere.

I Vavr erstatter vi hele kontakt blok med en Match metode. Hver sag eller hvis erklæring erstattes af en Sag metode påkaldelse.

Endelig, atommønstre som $() erstatte den betingelse, som derefter evaluerer et udtryk eller en værdi. Vi leverer dette også som den anden parameter til Sag:

@Test offentlig ugyldig nårMatchworks_thenCorrect () {int input = 2; String output = Match (input) .of (Case ($ (1), "one"), Case ($ (2), "two"), Case ($ (3), "three"), Case ($ ( ), "?")); assertEquals ("to", output); }

Læg mærke til, hvor kompakt koden er og kun have en linje pr. Check i gennemsnit. Mønster matchende API er langt mere kraftfuld end dette og kan gøre mere komplekse ting.

For eksempel kan vi erstatte atomudtrykkene med et predikat. Forestil dig, at vi analyserer en konsolkommando til Hjælp og version flag:

Match (arg) .of (Case ($ (isIn ("- h", "--help")), o -> run (this :: displayHelp)), Case ($ (isIn ("- v", " --version ")), o -> run (this :: displayVersion)), Case ($ (), o -> run (() -> {throw new IllegalArgumentException (arg);})));

Nogle brugere er måske mere fortrolige med stenografiversionen (-v), mens andre med den fulde version (–version). En god designer skal overveje alle disse sager.

Uden behov for flere hvis udsagn, vi har taget os af flere forhold.Vi lærer mere om prædikater, flere forhold og bivirkninger i mønstermatchning i en separat artikel.

10. Konklusion

I denne artikel har vi introduceret Vavr, det populære funktionelle programmeringsbibliotek til Java 8. Vi har tacklet de vigtigste funktioner, som vi hurtigt kan tilpasse for at forbedre vores kode.

Den fulde kildekode til denne artikel er tilgængelig i Github-projektet.


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