Introduksjon til Vavr

1. Oversikt

I denne artikkelen skal vi utforske nøyaktig hva Vavr er, hvorfor vi trenger det og hvordan vi kan bruke det i prosjektene våre.

Vavr er en funksjonelt bibliotek for Java 8+ som gir uforanderlige datatyper og funksjonelle kontrollstrukturer.

1.1. Maven avhengighet

For å bruke Vavr, må du legge til avhengigheten:

 io.vavr vavr 0.9.0 

Det anbefales å alltid bruke den nyeste versjonen. Du kan få det ved å følge denne lenken.

2. Alternativ

Hovedmålet med Option er å eliminere nullkontroller i koden vår ved å utnytte Java-typesystemet.

Alternativ er en objektbeholder i Vavr med et lignende sluttmål som Valgfritt i Java 8. Vavr's Alternativ redskaper Serialiserbar, Iterabel, og har et rikere API.

Siden enhver objektreferanse i Java kan ha en null verdi, må vi vanligvis sjekke om det er null hvis før du bruker den. Disse kontrollene gjør koden robust og stabil:

@Test offentlig ugyldig givenValue_whenNullCheckNeeded_thenCorrect () {Object possibleNullObj = null; if (possibleNullObj == null) {possibleNullObj = "someDefaultValue"; } assertNotNull (possibleNullObj); }

Uten sjekker kan applikasjonen krasje på grunn av en enkel NPE:

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

Imidlertid utfører sjekkene koden ordentlig og ikke så lesbar, spesielt når hvis uttalelser ender med å bli nestet flere ganger.

Alternativ løser dette problemet ved helt å eliminere null og erstatte dem med en gyldig objektreferanse for hvert mulige scenario.

Med Alternativ en null verdi vil evalueres til en forekomst av Ingen, mens en verdi som ikke er null, evalueres til en forekomst av Noen:

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

Derfor anbefales det å pakke dem inn i en i stedet for å bruke objektverdier direkte Alternativ eksempel som vist ovenfor.

Legg merke til at vi ikke måtte sjekke før vi ringte toString men vi måtte ikke forholde oss til en NullPointerException som vi hadde gjort før. Alternativ er toString gir oss meningsfulle verdier i hver samtale.

I den andre delen av denne delen trengte vi en null sjekk, der vi vil tilordne en standardverdi til variabelen, før vi prøver å bruke den. Alternativ kan takle dette i en enkelt linje, selv om det er null:

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

Eller en ikke-null:

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

Legg merke til hvordan, uten null sjekker, kan vi få en verdi eller returnere en standard i en enkelt linje.

3. Tuple

Det er ingen direkte ekvivalent med en tupledatastruktur i Java. En tuple er et vanlig begrep i funksjonelle programmeringsspråk. Tuples er uforanderlige og kan holde flere gjenstander av forskjellige typer på en typesikker måte.

Vavr bringer tupler til Java 8. Tupler er av typen Tuple1, Tuple2 til Tuple8 avhengig av antall elementer de skal ta.

Det er for øyeblikket en øvre grense på åtte elementer. Vi får tilgang til elementer av en tuple som tuple._n hvor n ligner forestillingen om en indeks i matriser:

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

Legg merke til at det første elementet er hentet med n == 1. Så en tuple bruker ikke en nullbase som en matrise. Typene av elementene som skal lagres i tupelen, må deklareres i dens typedeklarasjon som vist over og under:

@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 plass er å lagre en fast gruppe objekter av hvilken som helst type som er bedre behandlet som en enhet og som kan sendes rundt. En mer åpenbar brukstilfelle er å returnere mer enn ett objekt fra en funksjon eller en metode i Java.

4. Prøv

I Vavr, Prøve er en beholder for beregningsom kan resultere i et unntak.

Som Alternativ pakker et nullbart objekt slik at vi ikke trenger å eksplisitt ta vare på det null med hvis sjekker, Prøve pakker inn en beregning slik at vi ikke trenger å eksplisitt ta vare på unntak med prøvefangst blokker.

Ta følgende kode for eksempel:

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

Uten prøvefangst blokkerer, vil applikasjonen krasje. For å unngå dette, må du pakke uttalelsen i a prøvefangst blokkere. Med Vavr kan vi pakke den samme koden i en Prøve forekomst og få et resultat:

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

Hvorvidt beregningen var vellykket eller ikke, kan deretter inspiseres ved valg når som helst i koden.

I utdraget ovenfor har vi valgt å bare sjekke for suksess eller fiasko. Vi kan også velge å returnere en standardverdi:

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

Eller til og med å eksplisitt kaste et unntak fra vårt valg:

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

I alle de ovennevnte tilfellene har vi kontroll over hva som skjer etter beregningen, takket være Vavr's Prøve.

5. Funksjonelle grensesnitt

Med ankomsten av Java 8 er funksjonelle grensesnitt innebygd og enklere å bruke, spesielt når de kombineres med lambdas.

Imidlertid gir Java 8 bare to grunnleggende funksjoner. Man tar bare en enkelt parameter og produserer et resultat:

@Test offentlig ugyldig gittJava8Function_whenWorks_thenCorrect () {Funksjon kvadrat = (num) -> num * num; int resultat = kvadrat.apply (2); assertEquals (4, resultat); }

Den andre tar bare to parametere og gir et resultat:

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

På baksiden utvider Vavr ideen om funksjonelle grensesnitt i Java ytterligere ved å støtte opptil maksimalt åtte parametere og krydre API-en med metoder for memoisering, komposisjon og karri.

Akkurat som tupler, blir disse funksjonelle grensesnittene navngitt etter antall parametere de tar: Funksjon0, Funksjon 1, Funksjon 2 etc. Med Vavr ville vi ha skrevet de to ovennevnte funksjonene slik:

@Test offentlig tomrom gittVavrFunction_whenWorks_thenCorrect () {Funksjon1 kvadrat = (num) -> num * num; int resultat = kvadrat.apply (2); assertEquals (4, resultat); }

og dette:

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

Når det ikke er noen parameter, men vi fremdeles trenger en utgang, må vi i Java 8 bruke en Forbruker type, i Vavr Funksjon0 er der for å hjelpe:

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

Hva med en femparameterfunksjon, det er bare å bruke Funksjon 5:

@Test offentlig ugyldig nårCreatesFunction_thenCorrect5 () {Function5 concat = (a, b, c, d, e) -> a + b + c + d + e; String finalString = concat.apply ("Hei", "verden", "!", "Lær", "Vavr"); assertEquals ("Hello world! Learn Vavr", finalString); }

Vi kan også kombinere den statiske fabrikkmetoden FunksjonN.of for noen av funksjonene for å lage en Vavr-funksjon fra en metodereferanse. Som om vi har følgende sum metode:

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

Vi kan lage en funksjon ut av det slik:

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

6. Samlinger

Vavr-teamet har lagt ned mye arbeid i å designe et nytt API for samlinger som oppfyller kravene til funksjonell programmering, dvs.

Java-samlinger er mutable, noe som gjør dem til en stor kilde til programfeil, spesielt i nærvær av samtidighet. De Samling grensesnittet gir metoder som dette:

grensesnitt samling {void clear (); }

Denne metoden fjerner alle elementene i en samling (produserer en bivirkning) og returnerer ingenting. Klasser som ConcurrentHashMap ble opprettet for å håndtere de allerede opprettede problemene.

En slik klasse tilfører ikke bare null marginale fordeler, men forringer ytelsen til klassen hvis smutthull den prøver å fylle.

Med uforanderlighet får vi trådsikkerhet gratis: ingen grunn til å skrive nye klasser for å håndtere et problem som ikke burde være der i utgangspunktet.

Andre eksisterende taktikker for å legge til uforanderlighet i samlinger i Java skaper fortsatt flere problemer, nemlig unntak:

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

Alle ovennevnte problemer er ikke-eksisterende i Vavr-samlinger.

Slik oppretter du en liste i Vavr:

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

APIer er også tilgjengelige for å utføre beregninger på listen på plass:

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

Vavr-samlinger tilbyr de fleste vanlige klassene som finnes i Java Collections Framework, og faktisk er alle funksjonene implementert.

Takeaway er uforanderlighet, fjerning av ugyldige returtyper og bivirkningsproduserende API-er, et rikere sett med funksjoner for å operere på de underliggende elementene, veldig kort, robust og kompakt kode sammenlignet med Java's innsamlingsoperasjoner.

En full dekning av Vavr-samlinger ligger utenfor omfanget av denne artikkelen.

7. Validering

Vavr bringer begrepet Applikativ Functor til Java fra den funksjonelle programmeringsverdenen. Enkelt sagt en Applikativ Functor gjør oss i stand til å utføre en sekvens av handlinger mens vi samler resultatene.

Klassen vavr.control.Validation forenkler opphopning av feil. Husk at vanligvis slutter et program så snart det oppstår en feil.

Derimot, Validering fortsetter behandlingen og akkumulerer feilene for at programmet skal fungere som en batch.

Tenk på at vi registrerer brukere innen Navn og alder og vi vil ta alle innspill først og bestemme om vi skal lage et Person forekomme eller returnere en liste over feil. Her er vår Person klasse:

offentlig klasse Person {private Strengnavn; privat alder; // standardkonstruktører, settere og getters, toString}

Deretter lager vi en klasse som heter PersonValidator. Hvert felt vil bli validert etter en metode, og en annen metode kan brukes til å kombinere alle resultatene til en Validering forekomst:

class PersonValidator {String NAME_ERR = "Ugyldige tegn i navn:"; String AGE_ERR = "Alder må være minst 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]", ""); returner invalidChars.isEmpty ()? Validation.valid (name): Validation.invalid (NAME_ERR + invalidChars); } privat validering validateAge (int age) {retur alder <0? Validation.invalid (AGE_ERR): Validation.valid (age); }}

Regelen for alder er at det skal være et helt tall større enn 0 og regelen for Navn er at den ikke skal inneholde spesialtegn:

@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 ("Valid (Person [name = John Doe, age = 30])", valid.toString ()); assertEquals ("Ugyldig (Liste (Ugyldige tegn i navn:?! 4, Alder må være minst 0))", ugyldig.tilString ()); }

En gyldig verdi er inneholdt i a Validering. Gyldig For eksempel er en liste over valideringsfeil inneholdt i Validering. Ugyldig forekomst. Så enhver valideringsmetode må returnere en av de to.

Innsiden Validering. Gyldig er en forekomst av Person mens du er inne Validering. Ugyldig er en liste over feil.

8. Lat

Lat er en beholder som representerer en verdi beregnet lat, dvs. beregning utsettes til resultatet kreves. Videre blir den evaluerte verdien lagret eller memoisert og returnert igjen og igjen hver gang den er nødvendig uten å gjenta beregningen:

@Test offentlig ugyldighet givenFunction_whenEvaluatesWithLazy_thenCorrect () {Lazy lat = Lazy.of (Matematikk :: tilfeldig); assertFalse (lazy.isEvaluated ()); dobbelt val1 = lat.get (); assertTrue (lazy.isEvaluated ()); dobbelt val2 = lat.get (); assertEquals (val1, val2, 0.1); }

I eksemplet ovenfor er funksjonen vi vurderer Matematikk. Tilfeldig. Legg merke til at vi i andre linje sjekker verdien og innser at funksjonen ennå ikke er utført. Dette er fordi vi fremdeles ikke har vist interesse for returverdien.

I den tredje kodelinjen viser vi interesse for beregningsverdien ved å ringe Lat. Få. På dette punktet utfører funksjonen og Lat. Evaluert returnerer sant.

Vi fortsetter også og bekrefter memoiseringen av Lat ved å prøve å verdien igjen. Hvis funksjonen vi ga ble utført igjen, ville vi definitivt motta et annet tilfeldig tall.

Derimot, Lat igjen lazyly returnerer den opprinnelig beregnede verdien som den endelige påstanden bekrefter.

9. Mønstermatching

Mønstermatching er et innfødt konsept i nesten alle funksjonelle programmeringsspråk. Det er ikke noe slikt i Java foreløpig.

I stedet, når vi ønsker å utføre en beregning eller returnere en verdi basert på inngangen vi mottar, bruker vi flere hvis uttalelser for å løse riktig kode for å utføre:

@Test offentlig ugyldig nårIfWorksAsMatcher_thenCorrect () {int input = 3; Strengutgang; hvis (input == 0) {output = "null"; } hvis (input == 1) {output = "one"; } hvis (input == 2) {output = "two"; } if (input == 3) {output = "three"; } annet {output = "ukjent"; } assertEquals ("tre", output); }

Vi kan plutselig se koden som strekker seg over flere linjer mens vi bare sjekker tre saker. Hver sjekk tar opp tre linjer med kode. Hva om vi måtte sjekke opptil hundre saker, det ville dreie seg om 300 linjer, ikke hyggelig!

Et annet alternativ er å bruke a bytte om uttalelse:

@Test offentlig ugyldig nårSwitchWorksAsMatcher_thenCorrect () {int input = 2; Strengutgang; switch (input) {case 0: output = "zero"; gå i stykker; tilfelle 1: output = "one"; gå i stykker; tilfelle 2: utgang = "to"; gå i stykker; tilfelle 3: utgang = "tre"; gå i stykker; standard: output = "ukjent"; gå i stykker; } assertEquals ("two", output); }

Ikke noe bedre. Vi er fortsatt i gjennomsnitt 3 linjer per sjekk. Mye forvirring og potensial for bugs. Glemmer en gå i stykker klausul er ikke et problem på kompileringstidspunktet, men kan resultere i vanskelige å oppdage feil senere.

I Vavr erstatter vi hele bytte om blokker med en Kamp metode. Hver sak eller hvis uttalelse erstattes av en Sak metodeinnkallelse.

Til slutt, atommønstre som $() erstatt tilstanden som deretter evaluerer et uttrykk eller en verdi. Vi gir dette også som den andre parameteren til Sak:

@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); }

Legg merke til hvor kompakt koden er, og gjennomsnitt bare en linje per sjekk. Mønster matching API er mye kraftigere enn dette og kan gjøre mer komplekse ting.

For eksempel kan vi erstatte atomuttrykkene med et predikat. Tenk deg at vi analyserer en konsollkommando for hjelp og versjon flagg:

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);})));

Noen brukere kan være mer kjent med forkortelsesversjonen (-v), mens andre, med fullversjonen (–versjon). En god designer må vurdere alle disse sakene.

Uten behov for flere hvis uttalelser, har vi ivaretatt flere forhold.Vi vil lære mer om predikater, flere forhold og bivirkninger i mønstermatching i en egen artikkel.

10. Konklusjon

I denne artikkelen har vi introdusert Vavr, det populære funksjonelle programmeringsbiblioteket for Java 8. Vi har taklet de viktigste funksjonene som vi raskt kan tilpasse for å forbedre koden vår.

Hele kildekoden for denne artikkelen er tilgjengelig i Github-prosjektet.


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