Toegang krijgen tot een iteratieteller in een For Each-lus

1. Overzicht

Tijdens het itereren van gegevens in Java, willen we mogelijk toegang krijgen tot zowel het huidige item als zijn positie in de gegevensbron.

Dit is heel gemakkelijk te bereiken in een klassieker voor lus, waar de positie meestal de focus is van de berekeningen van de lus, maar het vereist wat meer werk als we constructies gebruiken zoals voor elke lus of stroom.

In deze korte tutorial zullen we een paar manieren bekijken voor elke operatie kan een teller bevatten.

2. Implementeren van een loket

Laten we beginnen met een eenvoudig voorbeeld. We nemen een geordende lijst met films en geven deze met hun rangschikking weer.

List IMDB_TOP_MOVIES = Arrays.asList ("The Shawshank Redemption", "The Godfather", "The Godfather II", "The Dark Knight");

2.1. voor Lus

EEN voor lus gebruikt een teller om naar het huidige item te verwijzen, dus het is een gemakkelijke manier om zowel de gegevens als de bijbehorende index in de lijst te gebruiken:

Lijstrangschikking = nieuwe ArrayList (); for (int i = 0; i <movies.size (); i ++) {String ranking = (i + 1) + ":" + movies.get (i); rankings.add (ranking); }

Zoals dit Lijst is waarschijnlijk een ArrayList, de krijgen werking is efficiënt, en de bovenstaande code is een eenvoudige oplossing voor ons probleem.

assertThat (getRankingsWithForLoop (IMDB_TOP_MOVIES)) .containsExactly ("1: The Shawshank Redemption", "2: The Godfather", "3: The Godfather II", "4: The Dark Knight");

Op deze manier kunnen echter niet alle gegevensbronnen in Java worden herhaald. Soms krijgen is een tijdrovende operatie, of we kunnen het volgende element van een gegevensbron alleen verwerken met Stroom of Herhaalbaar.

2.2. voor Elke lus

We zullen onze lijst met films blijven gebruiken, maar laten we doen alsof we er alleen over kunnen herhalen met Java's voor elke constructie:

voor (String movie: IMDB_TOP_MOVIES) {// gebruik filmwaarde}

Hier moeten we een aparte variabele gebruiken om de huidige index bij te houden. We kunnen dat buiten de lus construeren en het binnen verhogen:

int i = 0; voor (String movie: movies) {String ranking = (i + 1) + ":" + movie; rankings.add (ranking); i ++; }

Dat moeten we opmerken we moeten de teller verhogen nadat deze is gebruikt binnen de lus.

3. Een functioneel voor Elk

Het schrijven van de tellerextensie elke keer dat we deze nodig hebben, kan resulteren in codeduplicatie en kan het risico lopen op onbedoelde bugs over het bijwerken van de tellervariabele. We kunnen het bovenstaande daarom generaliseren met behulp van de functionele interfaces van Java.

Ten eerste moeten we het gedrag binnen de lus beschouwen als een consument van zowel het item in de collectie als ook de index. Dit kan worden gemodelleerd met BiConsumer, die een aanvaarden functie waaraan twee parameters moeten doorgegeven worden

@FunctionalInterface openbare interface BiConsumer {ongeldig accepteren (T t, U u); }

Omdat de binnenkant van onze lus iets is dat twee waarden gebruikt, zouden we een algemene lusoperatie kunnen schrijven. Het kan de Herhaalbaar van de brongegevens, waarover de for elke lus zal lopen, en de BiConsumer voor de bewerking die moet worden uitgevoerd op elk item en de bijbehorende index. We kunnen dit generiek maken met de parameter type T:

static void forEachWithCounter (herhaalbare bron, BiConsumer consument) {int i = 0; voor (T item: source) {consumer.accept (i, item); i ++; }}

We kunnen dit gebruiken met ons voorbeeld van filmrangschikkingen door de implementatie voor het BiConsumer als lambda:

Lijstrangschikking = nieuwe ArrayList (); forEachWithCounter (films, (i, film) -> {String ranking = (i + 1) + ":" + movies.get (i); rankings.add (ranking);});

4. Een teller toevoegen aan voor elk met Stroom

De Java Stroom API stelt ons in staat om uit te drukken hoe onze gegevens door filters en transformaties gaan. Het biedt ook een voor elk functie. Laten we proberen dat om te zetten in een bewerking met de teller.

De Stream voor elk functie heeft een Klant om het volgende item te verwerken. We zouden dat echter kunnen creëren Klant om de balie bij te houden en het item door te geven aan een BiConsumer:

openbare statische Consumer withCounter (BiConsumer consumer) {AtomicInteger counter = new AtomicInteger (0); item retourneren -> consumer.accept (counter.getAndIncrement (), item); }

Deze functie retourneert een nieuwe lambda. Die lambda gebruikt de AtomicInteger object om de teller tijdens iteratie bij te houden. De getAndIncrement functie wordt aangeroepen elke keer dat er een nieuw item is.

De lambda die door deze functie is gemaakt, wordt gedelegeerd naar de BiConsumer doorgegeven zodat het algoritme zowel het item als de index kan verwerken.

Laten we dit eens bekijken in gebruik door ons voorbeeld van filmrangschikking tegen een Stroom gebeld films:

Lijstrangschikking = nieuwe ArrayList (); movies.forEach (withCounter ((i, movie) -> {String ranking = (i + 1) + ":" + movie; rankings.add (ranking);}));

Binnen in de voor elk is een oproep aan de metCounter functie om een ​​object te maken dat zowel de telling bijhoudt als fungeert als de Klant dat de voor elk operatie passeert ook zijn waarden.

5. Conclusie

In dit korte artikel hebben we drie manieren bekeken om een ​​teller aan Java te koppelen voor elke operatie.

We hebben gezien hoe we de index van het huidige item bij elke implementatie ervan kunnen volgen voor een lus. We hebben vervolgens gekeken hoe we dit patroon konden generaliseren en hoe we het aan streaming-bewerkingen konden toevoegen.

Zoals altijd is de voorbeeldcode voor dit artikel beschikbaar op GitHub.