Gids voor Java BiFunction Interface

1. Inleiding

Java 8 introduceerde functionele stijlprogrammering, waardoor we algemene methoden kunnen parametriseren door functies door te geven.

We zijn waarschijnlijk het meest bekend met de functionele Java 8-interfaces met één parameter, zoals Functie, Predikaat, en Klant.

In deze tutorial gaan we kijken naar functionele interfaces die twee parameters gebruiken. Dergelijke functies worden binaire functies genoemd en worden in Java weergegeven met de BiFunction functionele interface.

2. Enkele parameterfuncties

Laten we snel samenvatten hoe we een enkele parameter of unaire functie gebruiken, zoals we doen in streams:

Lijst toegewezen = Stream.of ("hallo", "wereld") .map (woord -> woord + "!") .Collect (Collectors.toList ()); assertThat (toegewezen) .containsExactly ("hallo!", "wereld!");

Zoals we kunnen zien, is de kaart toepassingen Functie, die een enkele parameter nodig heeft en ons in staat stelt een bewerking uit te voeren op die waarde, waarbij een nieuwe waarde wordt geretourneerd.

3. Bewerkingen met twee parameters

De Java Stream-bibliotheek biedt ons een verminderen functie waarmee we de elementen van een stream kunnen combineren. We moeten uitdrukken hoe de waarden die we tot nu toe hebben verzameld, worden getransformeerd door het volgende item toe te voegen.

De verminderen functie maakt gebruik van de functionele interface BinaryOperator, waarvoor twee objecten van hetzelfde type nodig zijn als de invoer.

Laten we ons voorstellen dat we alle items in onze stream willen samenvoegen door de nieuwe vooraan te plaatsen met een streepjesscheidingsteken. In de volgende secties zullen we een paar manieren bekijken om dit te implementeren.

3.1. Met behulp van een Lambda

De implementatie van een lambda voor een BiFunction wordt voorafgegaan door twee parameters, omgeven door haakjes:

String resultaat = Stream.of ("hallo", "wereld") .reduce ("", (a, b) -> b + "-" + a); assertThat (resultaat) .isEqualTo ("world-hallo-");

Zoals we kunnen zien, zijn de twee waarden, een en b zijn Snaren. We hebben een lambda geschreven die ze combineert om de gewenste output te maken, met de tweede eerst, en een streepje ertussen.

Dat moeten we opmerken verminderen gebruikt een beginwaarde - in dit geval de lege string. We eindigen dus met een achterliggend streepje met de bovenstaande code, omdat de eerste waarde uit onze stream ermee wordt samengevoegd.

We moeten ook opmerken dat Java's type-inferentie ons in staat stelt om de typen van onze parameters meestal weg te laten. In situaties waarin het type van een lambda niet duidelijk is uit de context, kunnen we typen gebruiken voor onze parameters:

String resultaat = Stream.of ("hallo", "wereld") .reduce ("", (String a, String b) -> b + "-" + a);

3.2. Met behulp van een functie

Wat als we ervoor wilden zorgen dat het bovenstaande algoritme geen streepje op het einde zet? We zouden meer code in onze lambda kunnen schrijven, maar dat kan rommelig worden. Laten we in plaats daarvan een functie extraheren:

private String combWithoutTrailingDash (String a, String b) {if (a.isEmpty ()) {return b; } retourneer b + "-" + a; }

En noem het dan:

String resultaat = Stream.of ("hallo", "wereld") .reduce ("", (a, b) -> combinerenWithoutTrailingDash (a, b)); assertThat (resultaat) .isEqualTo ("world-hallo");

Zoals we kunnen zien, roept de lambda onze functie aan, die gemakkelijker te lezen is dan de meer complexe implementatie inline te zetten.

3.3. Met behulp van een methodeverwijzing

Sommige IDE's zullen ons automatisch vragen om de lambda hierboven om te zetten in een methodereferentie, omdat het vaak duidelijker is om te lezen.

Laten we onze code herschrijven om een ​​methodeverwijzing te gebruiken:

String resultaat = Stream.of ("hallo", "wereld") .reduce ("", this :: combinerenWithoutTrailingDash); assertThat (resultaat) .isEqualTo ("world-hallo");

Methodeverwijzingen maken de functionele code vaak meer vanzelfsprekend.

4. Met behulp van BiFunction

Tot nu toe hebben we gedemonstreerd hoe u functies kunt gebruiken waarbij beide parameters van hetzelfde type zijn. De BiFunction interface stelt ons in staat om parameters van verschillende typen te gebruiken, met een retourwaarde van een derde type.

Laten we ons voorstellen dat we een algoritme maken om twee lijsten van gelijke grootte te combineren tot een derde lijst door een bewerking uit te voeren op elk paar elementen:

Lijst lijst1 = Arrays.asList ("a", "b", "c"); Lijst lijst2 = Arrays.asList (1, 2, 3); Lijstresultaat = nieuwe ArrayList (); for (int i = 0; i <list1.size (); i ++) {result.add (list1.get (i) + list2.get (i)); } assertThat (resultaat) .containsExactly ("a1", "b2", "c3");

4.1. Generaliseer de functie

We kunnen deze gespecialiseerde functie generaliseren met behulp van een BiFunction als combiner:

private static 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))); } resultaat teruggeven; }

Eens kijken wat er hier aan de hand is. Er zijn drie soorten parameters: T voor het type item in de eerste lijst, U voor het type in de tweede lijst, en dan R voor welk type dan ook de combinatiefunctie retourneert.

Wij gebruiken de BiFunction geleverd aan deze functie door het aan te roepen van toepassing zijn methode om het resultaat te krijgen.

4.2. De gegeneraliseerde functie aanroepen

Onze combiner is een BiFunction, waarmee we een algoritme kunnen injecteren, ongeacht de soorten invoer en uitvoer. Laten we het uitproberen:

Lijst lijst1 = Arrays.asList ("a", "b", "c"); Lijst lijst2 = Arrays.asList (1, 2, 3); Lijstresultaat = listCombiner (lijst1, lijst2, (a, b) -> a + b); assertThat (resultaat) .containsExactly ("a1", "b2", "c3");

En we kunnen dit ook gebruiken voor totaal verschillende soorten inputs en outputs.

Laten we een algoritme injecteren om te bepalen of de waarde in de eerste lijst groter is dan de waarde in de tweede en een boolean resultaat:

Lijst lijst1 = Arrays.asList (1.0d, 2.1d, 3.3d); Lijst lijst2 = Arrays.asList (0.1f, 0.2f, 4f); Lijstresultaat = listCombiner (lijst1, lijst2, (a, b) -> a> b); assertThat (resultaat) .containsExactly (true, true, false);

4.3. EEN BiFunction Methode referentie

Laten we de bovenstaande code herschrijven met een geëxtraheerde methode en een referentiemethode:

Lijst lijst1 = Arrays.asList (1.0d, 2.1d, 3.3d); Lijst lijst2 = Arrays.asList (0.1f, 0.2f, 4f); Lijstresultaat = listCombiner (lijst1, lijst2, dit :: firstIsGreaterThanSecond); assertThat (resultaat) .containsExactly (true, true, false); private boolean firstIsGreaterThanSecond (Double a, Float b) {return a> b; }

We moeten opmerken dat dit de code een beetje gemakkelijker te lezen maakt, net als de methode firstIsGreaterThanSecond beschrijft het algoritme dat is geïnjecteerd als referentiemethode.

4.4. BiFunction Methodeverwijzingen met behulp van dit

Laten we ons voorstellen dat we het bovenstaande willen gebruiken BiFunctiegebaseerd algoritme om te bepalen of twee lijsten gelijk zijn:

Lijst lijst1 = Arrays.asList (0.1f, 0.2f, 4f); Lijst lijst2 = Arrays.asList (0.1f, 0.2f, 4f); Lijstresultaat = listCombiner (lijst1, lijst2, (a, b) -> a.equals (b)); assertThat (resultaat) .containsExactly (true, true, true);

We kunnen de oplossing eigenlijk vereenvoudigen:

Lijstresultaat = listCombiner (lijst1, lijst2, Float :: is gelijk aan);

Dit komt doordat de is gelijk aan functie in Vlotter heeft dezelfde handtekening als een BiFunction. Er is een impliciete eerste parameter nodig van dit, een object van type Vlotter. De tweede parameter, andere, van het type Voorwerp, is de waarde om te vergelijken.

5. Componeren BiFunctions

Wat als we methodeverwijzingen zouden kunnen gebruiken om hetzelfde te doen als ons voorbeeld van numerieke lijstvergelijking?

Lijst lijst1 = Arrays.asList (1.0d, 2.1d, 3.3d); Lijst lijst2 = Arrays.asList (0.1d, 0.2d, 4d); Lijstresultaat = listCombiner (list1, list2, Double :: CompareTo); assertThat (resultaat) .containsExactly (1, 1, -1);

Dit komt dicht bij ons voorbeeld, maar retourneert een Geheel getal, in plaats van het origineel Boolean. Dit komt doordat de vergelijk met methode in Dubbele geeft terug Geheel getal.

We kunnen het extra gedrag toevoegen dat we nodig hebben om ons origineel te bereiken door gebruik makend van en dan om een ​​functie samen te stellen. Dit levert een BiFunction dat doet eerst één ding met de twee ingangen en voert vervolgens een andere bewerking uit.

Laten we vervolgens een functie maken om onze methodereferentie af te dwingen Double :: CompareTo in een BiFunction:

privé statische BiFunction asBiFunction (BiFunction-functie) {retourfunctie; }

Een lambda- of methodeverwijzing wordt alleen een BiFunction nadat het is geconverteerd door een methode-aanroep. We kunnen deze helperfunctie gebruiken om onze lambda om te zetten in de BiFunction object expliciet.

Nu kunnen we gebruiken en dan om gedrag toe te voegen bovenop de eerste functie:

Lijst lijst1 = Arrays.asList (1.0d, 2.1d, 3.3d); Lijst lijst2 = Arrays.asList (0.1d, 0.2d, 4d); Lijstresultaat = listCombiner (list1, list2, asBiFunction (Double :: CompareTo) .andThen (i -> i> 0)); assertThat (resultaat) .containsExactly (true, true, false);

6. Conclusie

In deze tutorial hebben we onderzocht BiFunction en BinaryOperator in termen van de meegeleverde Java Streams-bibliotheek en onze eigen aangepaste functies. We hebben gekeken hoe we kunnen slagen BiFunctions met behulp van lambda's en methodeverwijzingen, en we hebben gezien hoe functies kunnen worden samengesteld.

De Java-bibliotheken bieden alleen functionele interfaces met één en twee parameters. Voor situaties die meer parameters vereisen, zie ons artikel over currying voor meer ideeën.

Zoals altijd zijn de volledige codevoorbeelden beschikbaar op GitHub.


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