Vejledning til Java BiFunction-interface

1. Introduktion

Java 8 introducerede funktionel stilprogrammering, der giver os mulighed for at parametrere generelle metoder ved at videregive funktioner.

Vi er sandsynligvis mest fortrolige med de enkeltparametre Java 8-funktionelle grænseflader som Fungere, Prædikat, og Forbruger.

I denne vejledning skal vi se på funktionelle grænseflader, der bruger to parametre. Sådanne funktioner kaldes binære funktioner og er repræsenteret i Java med BiFunction funktionel grænseflade.

2. Enkeltparameterfunktioner

Lad os hurtigt sammenfatte, hvordan vi bruger en enkeltparameter eller unariefunktion, som vi gør i streams:

Liste kortlagt = Stream.of ("hej", "verden"). Kort (ord -> ord + "!") .Collect (Collectors.toList ()); assertThat (kortlagt) .containsExactly ("hej!", "verden!");

Som vi kan se, er kort anvendelser Fungere, som tager en enkelt parameter og giver os mulighed for at udføre en operation på denne værdi og returnere en ny værdi.

3. Betjening med to parametre

Java Stream-biblioteket giver os en reducere funktion, der giver os mulighed for at kombinere elementerne i en stream. Vi er nødt til at udtrykke, hvordan de værdier, vi har akkumuleret hidtil, transformeres ved at tilføje det næste element.

Det reducere funktion bruger den funktionelle grænseflade BinaryOperator, som tager to objekter af samme type som dens input.

Lad os forestille os, at vi vil slutte os til alle elementerne i vores strøm ved at sætte de nye foran med en bindestregsskiller. Vi ser på et par måder at implementere dette på i de følgende afsnit.

3.1. Brug af en Lambda

Implementeringen af ​​en lambda til en BiFunction er forud for to parametre omgivet af parenteser:

Strengresultat = Stream.of ("hej", "verden") .reducer ("", (a, b) -> b + "-" + a); assertThat (resultat) .isEqualTo ("verden-hej-");

Som vi kan se, er de to værdier, -en og b er Strenge. Vi har skrevet en lambda, der kombinerer dem for at skabe det ønskede output, med den anden først og et strejf imellem.

Det skal vi bemærke reducere bruger en startværdi - i dette tilfælde den tomme streng. Således ender vi med et bageste strejf med ovenstående kode, da den første værdi fra vores strøm er forbundet med den.

Vi skal også bemærke, at Java's type inferens giver os mulighed for at udelade typerne af vores parametre det meste af tiden. I situationer hvor typen af ​​en lambda ikke er klar fra sammenhængen, kan vi bruge typer til vores parametre:

String result = Stream.of ("hej", "world") .reduce ("", (String a, String b) -> b + "-" + a);

3.2. Brug af en funktion

Hvad hvis vi ønskede at få ovenstående algoritme ikke til at sætte et strejf på enden? Vi kunne skrive mere kode i vores lambda, men det kan blive rodet. Lad os udtrække en funktion i stedet:

privat streng combineWithoutTrailingDash (streng a, streng b) {hvis (a.isEmpty ()) {return b; } returner b + "-" + a; }

Og så kald det:

Strengresultat = Stream.of ("hej", "verden") .reduce ("", (a, b) -> combineWithoutTrailingDash (a, b)); assertThat (resultat) .isEqualTo ("verden-hej");

Som vi kan se, kalder lambda vores funktion, som er lettere at læse end at sætte den mere komplekse implementering på plads.

3.3. Brug af en metodehenvisning

Nogle IDE'er vil automatisk bede os om at konvertere lambda ovenfor til en metodereference, da det ofte er tydeligere at læse.

Lad os omskrive vores kode for at bruge en metodereference:

Strengresultat = Stream.of ("hej", "verden") .reduce ("", dette :: combineWithoutTrailingDash); assertThat (resultat) .isEqualTo ("verdenshello");

Metodehenvisninger gør ofte den funktionelle kode mere selvforklarende.

4. Brug BiFunction

Indtil videre har vi demonstreret, hvordan man bruger funktioner, hvor begge parametre er af samme type. Det BiFunction interface giver os mulighed for at bruge parametre af forskellige typer, med en returværdi af en tredje type.

Lad os forestille os, at vi opretter en algoritme til at kombinere to lister af samme størrelse til en tredje liste ved at udføre en operation på hvert par af elementer:

Liste liste1 = Arrays.asList ("a", "b", "c"); Liste liste2 = Arrays.asList (1, 2, 3); Listeresultat = ny ArrayList (); for (int i = 0; i <list1.size (); i ++) {result.add (list1.get (i) + list2.get (i)); } assertThat (result) .containsExactly ("a1", "b2", "c3");

4.1. Generaliser funktionen

Vi kan generalisere denne specialiserede funktion ved hjælp af en BiFunction som kombinerer:

privat statisk List listCombiner (List list1, List list2, BiFunction combiner) {List result = new ArrayList (); for (int i = 0; i <list1.size (); i ++) {result.add (combiner.apply (list1.get (i), list2.get (i))); } returnere resultat }

Lad os se, hvad der foregår her. Der er tre typer parametre: T for den type vare på den første liste, U for typen i den anden liste, og derefter R uanset hvilken type kombinationsfunktionen returnerer.

Vi bruger BiFunction leveres til denne funktion ved at kalde dens ansøge metode for at få resultatet.

4.2. Opkald til den generaliserede funktion

Vores combiner er en BiFunction, som giver os mulighed for at indsprøjte en algoritme, uanset hvilke typer input og output. Lad os prøve det:

Liste liste1 = Arrays.asList ("a", "b", "c"); Liste liste2 = Arrays.asList (1, 2, 3); Listeresultat = listCombiner (liste1, liste2, (a, b) -> a + b); assertThat (resultat) .containsExactly ("a1", "b2", "c3");

Og vi kan også bruge dette til helt forskellige typer input og output.

Lad os injicere en algoritme for at bestemme, om værdien i den første liste er større end værdien i den anden og producere en boolsk resultat:

Liste liste1 = Arrays.asList (1.0d, 2.1d, 3.3d); Liste liste2 = Arrays.asList (0.1f, 0.2f, 4f); Listeresultat = listCombiner (liste1, liste2, (a, b) -> a> b); assertThat (resultat) .containsExactly (true, true, false);

4.3. EN BiFunction Metode Reference

Lad os omskrive ovenstående kode med en ekstraheret metode og en metodereference:

Liste liste1 = Arrays.asList (1.0d, 2.1d, 3.3d); Liste liste2 = Arrays.asList (0.1f, 0.2f, 4f); Listeresultat = listCombiner (liste1, liste2, dette :: firstIsGreaterThanSecond); assertThat (resultat) .containsExactly (true, true, false); privat boolsk firstIsGreaterThanSecond (Double a, Float b) {return a> b; }

Vi skal bemærke, at dette gør koden lidt lettere at læse, som metoden firstIsGreaterThanSecond beskriver den injicerede algoritme som en metodehenvisning.

4.4. BiFunction Metode Referencer Brug af det her

Lad os forestille os, at vi vil bruge ovenstående BiFunktion-baseret algoritme til at bestemme, om to lister er ens:

Liste liste1 = Arrays.asList (0.1f, 0.2f, 4f); Liste liste2 = Arrays.asList (0.1f, 0.2f, 4f); Listeresultat = listCombiner (liste1, liste2, (a, b) -> a. Ligestilling (b)); assertThat (resultat) .containsExactly (sandt, sandt, sandt);

Vi kan faktisk forenkle løsningen:

Listeresultat = listCombiner (liste1, liste2, flyde :: er lig med);

Dette skyldes, at lige med funktion i Flyde har den samme signatur som en BiFunction. Det kræver en implicit første parameter for det her, et objekt af typen Flyde. Den anden parameter, Andet, af typen Objekt, er den værdi, der skal sammenlignes.

5. Komponering BiFunktioner

Hvad hvis vi kunne bruge metodereferencer til at gøre det samme som vores eksempel på sammenligning af numeriske lister?

Liste liste1 = Arrays.asList (1.0d, 2.1d, 3.3d); Liste liste2 = Arrays.asList (0.1d, 0.2d, 4d); Liste resultat = listCombiner (liste1, liste2, dobbelt :: sammenlignTo); assertThat (resultat) .containsExactly (1, 1, -1);

Dette er tæt på vores eksempel, men returnerer et Heltal, snarere end originalen Boolsk. Dette skyldes, at sammenligne med metode i Dobbelt vender tilbage Heltal.

Vi kan tilføje den ekstra adfærd, vi har brug for for at opnå vores original ved brug af og så at komponere en funktion. Dette producerer en BiFunction der først gør en ting med de to indgange og derefter udfører en anden handling.

Lad os derefter oprette en funktion til at tvinge vores metodehenvisning Dobbelt :: sammenlignTo ind i en BiFunction:

privat statisk BiFunction som BiFunction (BiFunction-funktion) {returfunktion; }

En lambda- eller metodehenvisning bliver kun en BiFunction efter at den er blevet konverteret ved en metodeopkald. Vi kan bruge denne hjælperfunktion til at konvertere vores lambda til BiFunction objekt eksplicit.

Nu kan vi bruge og så for at tilføje adfærd oven på den første funktion:

Liste liste1 = Arrays.asList (1.0d, 2.1d, 3.3d); Liste liste2 = Arrays.asList (0.1d, 0.2d, 4d); Listeresultat = listCombiner (liste1, liste2, asBiFunction (dobbelt :: sammenlignTo) .ogThen (i -> i> 0)); assertThat (resultat) .containsExactly (true, true, false);

6. Konklusion

I denne vejledning har vi udforsket BiFunction og BinaryOperator med hensyn til det medfølgende Java Streams-bibliotek og vores egne brugerdefinerede funktioner. Vi har set på, hvordan vi passerer BiFunktioner ved hjælp af lambdas og metodehenvisninger, og vi har set, hvordan man komponerer funktioner.

Java-bibliotekerne har kun funktionelle grænseflader med en og to parametre. For situationer, der kræver flere parametre, se vores artikel om curry for flere ideer.

Som altid er de komplette kodeeksempler tilgængelige på GitHub.