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 评论