如何在每個循環中訪問迭代計數器
- java
- Stream
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
是一項耗時的操作,或者我們只能使用Stream
或Iterable.
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
一個循環。然後,我們研究瞭如何概括這種模式以及如何將其添加到流操作中。