Inleiding tot Vavr

1. Overzicht

In dit artikel gaan we onderzoeken wat Vavr precies is, waarom we het nodig hebben en hoe we het in onze projecten kunnen gebruiken.

Vavr is een functionele bibliotheek voor Java 8+ die onveranderlijke gegevenstypen en functionele controlestructuren biedt.

1.1. Afhankelijkheid van Maven

Om Vavr te gebruiken, moet u de afhankelijkheid toevoegen:

 io.vavr vavr 0.9.0 

Het wordt aanbevolen om altijd de laatste versie te gebruiken. U kunt het verkrijgen door deze link te volgen.

2. Optie

Het belangrijkste doel van Option is om nulcontroles in onze code te elimineren door gebruik te maken van het Java-type systeem.

Keuze is een objectcontainer in Vavr met een vergelijkbaar einddoel als Optioneel in Java 8. Vavr's Keuze werktuigen Serialiseerbaar, herhaalbaar, en heeft een rijkere API.

Omdat elke objectverwijzing in Java een nul waarde, we moeten meestal controleren op nietigheid met als verklaringen voordat u het gebruikt. Deze controles maken de code robuust en stabiel:

@Test openbare ongeldig gegevenValue_whenNullCheckNeeded_thenCorrect () {Object mogelijkNullObj = null; if (PossibleNullObj == null) {PossibleNullObj = "someDefaultValue"; } assertNotNull (mogelijkNullObj); }

Zonder controles kan de applicatie crashen als gevolg van een simpele NPE:

@Test (verwacht = NullPointerException.class) openbare ongeldige gegevenValue_whenNullCheckNeeded_thenCorrect2 () {Object mogelijkNullObj = null; assertEquals ("somevalue", mogelijkNullObj.toString ()); }

De controles maken echter de code uitgebreid en niet zo leesbaar, vooral wanneer de als verklaringen worden uiteindelijk meerdere keren genest.

Keuze lost dit probleem op door volledig te elimineren nulls en ze te vervangen door een geldige objectreferentie voor elk mogelijk scenario.

Met Keuze een nul waarde wordt geëvalueerd naar een exemplaar van Geen, terwijl een niet-null-waarde wordt geëvalueerd naar een instantie van Sommige:

@Test openbare ongeldige gegevenValue_whenCreatesOption_thenCorrect () {Option noneOption = Option.of (null); Optie someOption = Option.of ("val"); assertEquals ("None", noneOption.toString ()); assertEquals ("Some (val)", someOption.toString ()); }

In plaats van objectwaarden rechtstreeks te gebruiken, is het daarom raadzaam om ze in een Keuze instantie zoals hierboven weergegeven.

Merk op dat we geen controle hoefden uit te voeren voordat we belden toString toch hadden we niet te maken met een NullPointerException zoals we eerder hadden gedaan. Opties toString geeft ons betekenisvolle waarden in elke aanroep.

In het tweede fragment van deze sectie hadden we een nul check, waarin we een standaardwaarde aan de variabele toewijzen, voordat we deze proberen te gebruiken. Keuze kan dit op één regel afhandelen, zelfs als er een nul is:

@Test openbare leegte gegevenNull_whenCreatesOption_thenCorrect () {String name = null; Optie nameOption = Option.of (naam); assertEquals ("baeldung", nameOption.getOrElse ("baeldung")); }

Of een niet-nul:

@Test openbare leegte gegevenNonNull_whenCreatesOption_thenCorrect () {String name = "baeldung"; Optie nameOption = Option.of (naam); assertEquals ("baeldung", nameOption.getOrElse ("notbaeldung")); }

Merk op hoe, zonder nul controles, kunnen we een waarde krijgen of een standaard retourneren op een enkele regel.

3. Tupel

Er is geen direct equivalent van een tuple-datastructuur in Java. Een tuple is een veelgebruikt concept in functionele programmeertalen. Tuples zijn onveranderlijk en kunnen op een typeveilige manier meerdere objecten van verschillende typen bevatten.

Vavr brengt tuples naar Java 8. Tuples zijn van het type Tuple1, Tuple2 naar Tuple8 afhankelijk van het aantal elementen dat ze moeten nemen.

Er is momenteel een bovengrens van acht elementen. We hebben toegang tot elementen van een tupel zoals tupel._n waar n is vergelijkbaar met het idee van een index in arrays:

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

Merk op dat het eerste element wordt opgehaald met n == 1. Een tuple gebruikt dus geen nulbasis zoals een array. De typen elementen die in het tuple worden opgeslagen, moeten in de typeverklaring worden aangegeven, zoals hierboven en hieronder wordt weergegeven:

@Test openbare leegte whenCreatesTuple_thenCorrect2 () {Tuple3 java8 = Tuple.of ("Java", 8, 1.8); String element1 = java8._1; int element2 = java8._2 (); dubbel element3 = java8._3 (); assertEquals ("Java", element1); assertEquals (8, element2); assertEquals (1.8, element3, 0.1); }

De plaats van een tuple is het opslaan van een vaste groep objecten van elk type die beter als een eenheid kunnen worden verwerkt en kunnen worden doorgegeven. Een meer voor de hand liggende use case retourneert meer dan één object van een functie of methode in Java.

4. Probeer

In Vavr, Proberen is een container voor een berekeningwat kan resulteren in een uitzondering.

Net zo Keuze wraps een nullable object zodat we niet expliciet voor hoeven te zorgen nulls met als cheques, Proberen wikkelt een berekening zodat we niet expliciet voor uitzonderingen hoeven te zorgen met proberen te vangen blokken.

Neem bijvoorbeeld de volgende code:

@Test (verwacht = ArithmeticException.class) openbare ongeldige gegevenBadCode_whenThrowsException_thenCorrect () {int i = 1/0; }

Zonder proberen te vangen blokkeert, zou de applicatie crashen. Om dit te voorkomen, moet u de verklaring in een proberen te vangen blok. Met Vavr kunnen we dezelfde code in een Proberen instantie en krijg een resultaat:

@Test openbare ongeldige gegevenBadCode_whenTryHandles_thenCorrect () {Probeer resultaat = Try.of (() -> 1/0); assertTrue (result.isFailure ()); }

Of de berekening succesvol was of niet, kan vervolgens op elk punt in de code naar keuze worden geïnspecteerd.

In het bovenstaande fragment hebben we ervoor gekozen om eenvoudig te controleren op succes of mislukking. We kunnen er ook voor kiezen om een ​​standaardwaarde te retourneren:

@Test openbare ongeldige gegevenBadCode_whenTryHandles_thenCorrect2 () {Probeer berekening = Try.of (() -> 1/0); int errorSentinel = result.getOrElse (-1); assertEquals (-1, errorSentinel); }

Of zelfs om expliciet een uitzondering van onze keuze te gooien:

@Test (verwacht = ArithmeticException.class) openbare ongeldige gegevenBadCode_whenTryHandles_thenCorrect3 () {Probeer resultaat = Try.of (() -> 1/0); result.getOrElseThrow (ArithmeticException :: nieuw); }

In alle bovenstaande gevallen hebben we controle over wat er gebeurt na de berekening, dankzij Vavr's Proberen.

5. Functionele interfaces

Met de komst van Java 8 zijn functionele interfaces ingebouwd en gemakkelijker te gebruiken, vooral in combinatie met lambda's.

Java 8 biedt echter slechts twee basisfuncties. Men neemt slechts een enkele parameter en levert een resultaat op:

@Test openbare leegte gegevenJava8Function_whenWorks_thenCorrect () {Function square = (num) -> num * num; int resultaat = vierkant.apply (2); assertEquals (4, resultaat); }

De tweede heeft slechts twee parameters en levert een resultaat op:

@Test openbare ongeldig gegeven Java8BiFunction_whenWorks_thenCorrect () {BiFunction sum = (num1, num2) -> num1 + num2; int resultaat = som.apply (5, 7); assertEquals (12, resultaat); }

Aan de andere kant breidt Vavr het idee van functionele interfaces in Java verder uit door maximaal acht parameters te ondersteunen en de API op te fleuren met methoden voor memoisatie, compositie en currying.

Net als tuples krijgen deze functionele interfaces een naam op basis van het aantal parameters dat ze gebruiken: Functie0, Functie 1, Functie 2 etc. Met Vavr zouden we de bovenstaande twee functies als volgt hebben geschreven:

@Test openbare leegte gegevenVavrFunction_whenWorks_thenCorrect () {Function1 square = (num) -> num * num; int resultaat = vierkant.apply (2); assertEquals (4, resultaat); }

en dit:

@Test openbare leegte gegevenVavrBiFunction_whenWorks_thenCorrect () {Function2 sum = (num1, num2) -> num1 + num2; int resultaat = som.apply (5, 7); assertEquals (12, resultaat); }

Als er geen parameter is maar we toch een uitvoer nodig hebben, moeten we in Java 8 een Klant typ, in Vavr Functie0 is er om te helpen:

@Test openbare leegte whenCreatesFunction_thenCorrect0 () {Function0 getClazzName = () -> this.getClass (). GetName (); String clazzName = getClazzName.apply (); assertEquals ("com.baeldung.vavr.VavrTest", clazzName); }

Wat dacht je van een functie met vijf parameters, het is gewoon een kwestie van gebruiken Functie 5:

@Test openbare leegte whenCreatesFunction_thenCorrect5 () {Function5 concat = (a, b, c, d, e) -> a + b + c + d + e; String finalString = concat.apply ("Hallo", "wereld", "!", "Leren", "Vavr"); assertEquals ("Hallo wereld! Leer Vavr", finalString); }

We kunnen ook de statische fabrieksmethode combineren FunctieN.of voor een van de functies om een ​​Vavr-functie te maken op basis van een methodeverwijzing. Alsof we het volgende hebben som methode:

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

We kunnen er als volgt een functie van maken:

@Test openbare leegte whenCreatesFunctionFromMethodRef_thenCorrect () {Function2 sum = Function2.of (this :: sum); int opgeteld = som.apply (5, 6); assertEquals (11, opgeteld); }

6. Collecties

Het Vavr-team heeft veel energie gestoken in het ontwerpen van een nieuwe API voor collecties die voldoet aan de eisen van functioneel programmeren, d.w.z. persistentie, onveranderlijkheid.

Java-verzamelingen zijn veranderlijk, waardoor ze een grote bron van programmafouten zijn, vooral in aanwezigheid van gelijktijdigheid. De Verzameling interface biedt methoden zoals deze:

interface Collectie {void clear (); }

Deze methode verwijdert alle elementen in een verzameling (wat een bijwerking oplevert) en retourneert niets. Klassen zoals ConcurrentHashMap zijn gemaakt om de reeds gecreëerde problemen aan te pakken.

Zo'n klasse voegt niet alleen nul marginale voordelen toe, maar verslechtert ook de prestaties van de klasse waarvan ze de mazen in de wet probeert op te vullen.

Met onveranderlijkheid krijgen we gratis draadveiligheid: het is niet nodig om nieuwe klassen te schrijven om een ​​probleem aan te pakken dat er in de eerste plaats niet zou moeten zijn.

Andere bestaande tactieken om onveranderlijkheid aan collecties in Java toe te voegen, zorgen nog steeds voor meer problemen, namelijk uitzonderingen:

@Test (verwacht = UnsupportedOperationException.class) public void whenImmutableCollectionThrows_thenCorrect () {java.util.List wordList = Arrays.asList ("abracadabra"); java.util.List lijst = Collections.unmodifiableList (wordList); list.add ("boem"); }

Alle bovenstaande problemen komen niet voor in Vavr-collecties.

Om een ​​lijst in Vavr te maken:

@Test openbare leegte whenCreatesVavrList_thenCorrect () {List intList = List.of (1, 2, 3); assertEquals (3, intList.length ()); assertEquals (new Integer (1), intList.get (0)); assertEquals (nieuw geheel getal (2), intList.get (1)); assertEquals (nieuw geheel getal (3), intList.get (2)); }

API's zijn ook beschikbaar om berekeningen uit te voeren op de bestaande lijst:

@Test openbare leegte whenSumsVavrList_thenCorrect () {int sum = List.of (1, 2, 3) .sum (). IntValue (); assertEquals (6, som); }

Vavr-collecties bieden de meeste algemene klassen die worden aangetroffen in het Java Collections Framework en eigenlijk zijn alle functies geïmplementeerd.

De afhaalmaaltijd is onveranderlijkheid, verwijdering van ongeldige retourtypes en neveneffect producerende API's, een rijkere set van functies om te werken op de onderliggende elementen, zeer kort, robuust en compacte code vergeleken met Java's inzamelingsoperaties.

Een volledige dekking van Vavr-collecties valt buiten het bestek van dit artikel.

7. Validatie

Vavr brengt het concept van Toepasselijke functor naar Java vanuit de functionele programmeerwereld. In de eenvoudigste bewoordingen, een Toepasselijke functor stelt ons in staat om een ​​reeks acties uit te voeren terwijl we de resultaten verzamelen.

De klas vavr.control.Validatie vergemakkelijkt de opeenstapeling van fouten. Onthoud dat een programma gewoonlijk wordt beëindigd zodra er een fout optreedt.

Echter, Validatie gaat door met het verwerken en verzamelen van de fouten zodat het programma er als een batch op kan reageren.

Bedenk dat we gebruikers registreren door naam en leeftijd en we willen eerst alle invoer nemen en beslissen of we een Persoon instantie of retourneer een lijst met fouten. Hier is onze Persoon klasse:

openbare klasse Persoon {privé Stringnaam; privé int leeftijd; // standaard constructeurs, setters en getters, toString}

Vervolgens maken we een klasse met de naam PersoonValidator. Elk veld wordt gevalideerd door één methode en een andere methode kan worden gebruikt om alle resultaten tot één te combineren Validatie voorbeeld:

class PersonValidator {String NAME_ERR = "Ongeldige tekens in naam:"; String AGE_ERR = "Leeftijd moet minimaal 0 zijn"; openbare validatie validatePerson (String naam, int leeftijd) {return Validation.combine (validateName (naam), validateAge (leeftijd)). ap (Persoon :: nieuw); } private Validation validateName (String naam) {String invalidChars = name.replaceAll ("[a-zA-Z]", ""); return invalidChars.isEmpty ()? Validation.valid (naam): Validation.invalid (NAME_ERR + invalidChars); } private Validation validateAge (int age) {return age <0? Validation.invalid (AGE_ERR): Validation.valid (leeftijd); }}

De regel voor leeftijd is dat het een geheel getal groter dan 0 moet zijn en de regel voor naam is dat het geen speciale tekens mag bevatten:

@ Test openbaar ongeldig whenValidationWorks_thenCorrect () {PersonValidator personValidator = nieuwe PersonValidator (); Validatie valid = personValidator.validatePerson ("John Doe", 30); Validatie invalid = personValidator.validatePerson ("John? Doe! 4", -1); assertEquals ("Valid (Persoon [naam = John Doe, leeftijd = 30])", valid.toString ()); assertEquals ("Invalid (List (Invalid characters in name:?! 4, Age must be minimum 0))", invalid.toString ()); }

Een geldige waarde is opgenomen in een Validatie Geldig een lijst met validatiefouten is bijvoorbeeld opgenomen in een Validatie Ongeldig voorbeeld. Dus elke validatiemethode moet een van de twee retourneren.

Binnen Validatie Geldig is een voorbeeld van Persoon terwijl binnen Validatie Ongeldig is een lijst met fouten.

8. Lui

Lui is een container die een waarde vertegenwoordigt die lui wordt berekend, d.w.z. de berekening wordt uitgesteld totdat het resultaat vereist is. Bovendien wordt de geëvalueerde waarde in het cachegeheugen of in een memo opgeslagen en keer op keer geretourneerd wanneer deze nodig is zonder de berekening te herhalen:

@Test openbare leegte gegevenFunction_whenEvaluatesWithLazy_thenCorrect () {Lazy lazy = Lazy.of (Math :: random); assertFalse (lazy.isEvaluated ()); dubbele val1 = lui.get (); assertTrue (lazy.isEvaluated ()); dubbele waarde2 = lui.get (); assertEquals (val1, val2, 0.1); }

In het bovenstaande voorbeeld is de functie die we evalueren Math.willekeurig. Merk op dat we in de tweede regel de waarde controleren en beseffen dat de functie nog niet is uitgevoerd. Dit komt omdat we nog steeds geen interesse hebben getoond in de retourwaarde.

In de derde regel code tonen we interesse in de berekeningswaarde door te bellen Lazy.get. Op dit punt voert de functie en uit Lazy. Geëvalueerd geeft true terug.

We gaan ook door en bevestigen het memo-gedeelte van Lui door te proberen krijgen de waarde weer. Als de door ons verstrekte functie opnieuw zou worden uitgevoerd, zouden we zeker een ander willekeurig getal krijgen.

Echter, Lui geeft weer lui de aanvankelijk berekende waarde terug zoals de laatste bewering bevestigt.

9. Patroonaanpassing

Patroonherkenning is een native concept in bijna alle functionele programmeertalen. Op dit moment bestaat zoiets niet in Java.

In plaats daarvan gebruiken we meerdere wanneer we een berekening willen uitvoeren of een waarde willen retourneren op basis van de invoer die we ontvangen als verklaringen om de juiste code op te lossen om uit te voeren:

@Test openbare leegte whenIfWorksAsMatcher_thenCorrect () {int input = 3; String-uitvoer; if (input == 0) {output = "nul"; } if (input == 1) {output = "één"; } if (input == 2) {output = "twee"; } if (input == 3) {output = "three"; } else {output = "onbekend"; } assertEquals ("drie", output); }

We kunnen plotseling zien dat de code meerdere regels beslaat terwijl we slechts drie gevallen controleren. Elke controle beslaat drie regels code. Wat als we tot honderd gevallen zouden moeten controleren, dat zouden ongeveer 300 regels zijn, niet leuk!

Een ander alternatief is het gebruik van een schakelaar uitspraak:

@Test openbare leegte whenSwitchWorksAsMatcher_thenCorrect () {int input = 2; String-uitvoer; schakelaar (input) {case 0: output = "nul"; breken; case 1: output = "one"; breken; geval 2: output = "twee"; breken; geval 3: output = "drie"; breken; default: output = "onbekend"; breken; } assertEquals ("twee", output); }

Niet veel beter. We hebben nog steeds gemiddeld 3 regels per cheque. Veel verwarring en potentieel voor bugs. Vergeten een breken clausule is geen probleem tijdens het compileren, maar kan later leiden tot moeilijk te detecteren bugs.

In Vavr vervangen we het geheel schakelaar blok met een Bij elkaar passen methode. Elk geval of als statement wordt vervangen door een Geval methode aanroep.

Ten slotte, atoompatronen zoals $() vervang de voorwaarde die vervolgens een uitdrukking of waarde evalueert. We bieden dit ook als de tweede parameter aan Geval:

@ Test openbare ongeldig whenMatchworks_thenCorrect () {int input = 2; String output = Match (input) .of (Case ($ (1), "one"), Case ($ (2), "two"), Case ($ (3), "three"), Case ($ ( ), "?")); assertEquals ("twee", output); }

Merk op hoe compact de code is, gemiddeld slechts één regel per cheque. De patroonafstemmings-API is veel krachtiger dan dit en kan complexere dingen doen.

We kunnen bijvoorbeeld de atomaire uitdrukkingen vervangen door een predikaat. Stel je voor dat we een console-opdracht ontleden voor helpen en versie vlaggen:

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

Sommige gebruikers zijn wellicht meer bekend met de verkorte versie (-v), terwijl anderen de volledige versie (–versie) kennen. Een goede ontwerper moet al deze gevallen in overweging nemen.

Zonder dat er meerdere nodig zijn als verklaringen hebben wij voor meerdere voorwaarden gezorgd.We zullen in een apart artikel meer leren over predikaten, meerdere aandoeningen en bijwerkingen bij het matchen van patronen.

10. Conclusie

In dit artikel hebben we Vavr geïntroduceerd, de populaire functionele programmeerbibliotheek voor Java 8. We hebben de belangrijkste functies aangepakt die we snel kunnen aanpassen om onze code te verbeteren.

De volledige broncode voor dit artikel is beschikbaar in het Github-project.