如何在每個循環中訪問迭代計數器

1.概述

在Java中遍歷數據時,我們可能希望同時訪問當前項及其在數據源中的位置。

for循環中很容易實現,在經典的for循環中,位置通常是循環計算的重點,但是當我們為每個循環或流使用類似的構造時,它需要做更多的工作。

在這個簡短的教程中,我們將看看幾個方法是for每一個操作可以包括計數器。

2.實現計數器

讓我們從一個簡單的例子開始。我們將獲取電影的有序列表,並輸出其排名。

List<String> IMDB_TOP_MOVIES = Arrays.asList("The Shawshank Redemption",

 "The Godfather", "The Godfather II", "The Dark Knight");

2.1。 for循環

for循環使用一個計數器來引用當前項目,因此這是一種對列表中的數據及其索引進行操作的簡便方法:

List rankings = new ArrayList<>();

 for (int i = 0; i < movies.size(); i++) {

 String ranking = (i + 1) + ": " + movies.get(i);

 rankings.add(ranking);

 }

由於此List可能是ArrayList ,因此get操作很有效,並且上面的代碼是解決我們問題的簡單方法。

assertThat(getRankingsWithForLoop(IMDB_TOP_MOVIES))

 .containsExactly("1: The Shawshank Redemption",

 "2: The Godfather", "3: The Godfather II", "4: The Dark Knight");

但是,並非所有Java數據源都可以通過這種方式進行迭代。有時get是一項耗時的操作,或者我們只能使用StreamIterable.

2.2。 for每個循環

我們將繼續使用電影列表,但假設我們只能對每個結構使用Java對其進行迭代:

for (String movie : IMDB_TOP_MOVIES) {

 // use movie value

 }

在這裡,我們需要使用一個單獨的變量來跟踪當前索引。我們可以在循環外部構造它,然後在內部增加它:

int i = 0;

 for (String movie : movies) {

 String ranking = (i + 1) + ": " + movie;

 rankings.add(ranking);



 i++;

 }

我們應該注意,在循環中使用完計數器後,我們必須增加它的數量。

3.一種功能forEach

每次我們需要編寫計數器擴展名時,都可能導致代碼重複,並可能冒著有關何時更新計數器變量的意外錯誤的風險。因此,我們可以使用Java的功能接口來概括上述內容。

首先,我們應該將循環內部的行為視為集合中項目和索引的使用者。可以使用BiConsumer進行建模,該函數定義了一個accept函數,該函數接受兩個參數

@FunctionalInterface

 public interface BiConsumer<T, U> {

 void accept(T t, U u);

 }

由於循環的內部使用了兩個值,因此我們可以編寫一個通用的循環操作。它可能需要Iterable (每個循環將在BiConsumer ,以對每個項目及其索引執行該操作。我們可以使用類型參數T來使它通用:

static <T> void forEachWithCounter(Iterable<T> source, BiConsumer<Integer, T> consumer) {

 int i = 0;

 for (T item : source) {

 consumer.accept(i, item);

 i++;

 }

 }

BiConsumer的實現提供為lambda,我們可以在電影排名示例中使用它:

List rankings = new ArrayList<>();

 forEachWithCounter(movies, (i, movie) -> {

 String ranking = (i + 1) + ": " + movies.get(i);

 rankings.add(ranking);

 });

4.流實現添加一個計數器來forEach

Java Stream API允許我們表達數據如何通過過濾器和轉換。它還提供了forEach函數。讓我們嘗試將其轉換為包含計數器的操作。

Stream forEach函數需要Consumer處理下一個項目。但是,我們可以創建該Consumer來跟踪計數器並將該項目傳遞到BiConsumer

public static <T> Consumer<T> withCounter(BiConsumer<Integer, T> consumer) {

 AtomicInteger counter = new AtomicInteger(0);

 return item -> consumer.accept(counter.getAndIncrement(), item);

 }

該函數返回一個新的lambda。該lambda使用AtomicInteger對像在迭代過程中跟踪計數器。每次有新項目時都會調用getAndIncrement

由該函數創建的lambda委託給BiConsumer ,以便算法可以處理項目及其索引。

讓我們來看看在使用我們的電影排名對一個例子Stream稱為movies

List rankings = new ArrayList<>();

 movies.forEach(withCounter((i, movie) -> {

 String ranking = (i + 1) + ": " + movie;

 rankings.add(ranking);

 }));

forEach withCounter函數的調用,以創建一個對象,該對象既可以跟踪計數,又可以充當Consumer ,因為forEach操作也可以傳遞其值。

5.結論

在這篇簡短的文章中,我們研究for每種操作將計數器附加到Java的三種方法。

我們看到了如何跟踪他們每個執行當前項目的索引for一個循環。然後,我們研究瞭如何概括這種模式以及如何將其添加到流操作中。