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一个循环。然后,我们研究了如何概括这种模式以及如何将其添加到流操作中。
0 评论